How can I create some push notification with primefaces p:push only for an entity with ID =1 from my database?
In more detail: I have in my database a table "device". This means every time if a computer / tablet connects to my webapplication a new device will be stored in my database.
I have also a SessionBean for the device. This means a connected device knows "I am device with ID = 1".
Now I would like to send a push notification only to this device with ID = 1
Can anybody help me, please?
Thank you very much.
EDIT:
Bean Classes: NotifyResource and NotifyView
#PushEndpoint("/{macAddress}")
#Singleton
public class NotifyResource {
#PathParam(value = "macAddress")
private String macAddress;
#OnMessage(decoders = JSONDecoder.class, encoders = JSONEncoder.class)
public void onMessage(RemoteEndpoint r, EventBus eventBus) {
System.out.println("pushing to " + macAddress);
}
#RequestScoped
#RequestScoped
public class NotifyView {
#Inject
private DeviceBean deviceBean;
public void send() {
EventBus eventBus = EventBusFactory.getDefault().eventBus();
String macAddress = deviceBean.getDevice().getMacAddress();
byte[] bytesEncoded = Base64.encodeBase64(macAddress .getBytes());
eventBus.publish("/" + new String(bytesEncoded), new FacesMessage("Test", "Tasasas"));
}
}
JSF PAGE
<p:growl widgetVar="growl" showDetail="true" />
<p:socket onMessage="handleMessage" channel="/#{deviceBean.device.macAddress}" autoConnect="true" widgetVar='subscriber'/>
<script type="text/javascript">
function handleMessage(facesmessage) {
facesmessage.severity = 'info';
PF('growl').show([facesmessage]);
}
You can actually define placeholders on your Push Endpoint. Generally speaking, you can always put multiple users in a single push group. By putting each user in the group "/anything/hisOwnId", you make sure, each user has his/her own channel, since the ID is unique...
#PushEndpoint("{macAddress}")
#Singleton
public class PushResource
{
#PathParam(value = "macAddress")
private String macAddress;
#OnMessage(decoders = JSONDecoder.class, encoders = JSONEncoder.class)
public void onMessage(RemoteEndpoint r, EventBus eventBus) {
System.out.println("pushing to " + macAddress);
}
....
}
In your xhtml, you define your p:socket, in this case I've set it NOT to automatically connect.
To connect to the specific channel, the following JavaScript command can be used:
PF('subscriber').connect('/5') /* Id 5 */
With PrimeFaces, you can also execute JavaScript command from backing beans using RequestContext.getCurrentInstance().execute("command");
You could probably auto connect, too, but you have to make sure the user is logged in, to retreive the ID.
<!-- Not sure, if there has to be a forward slash in the beginning -->
<p:socket onMessage="handleMessage" channel="#{currentUser.id}" autoConnect="true" widgetVar='subscriber' />
<script type="text/javascript">
function handleMessage(msg) {
alert('received push');
}
</script>
To push a message to the specific channel (the user id in your case)
private final EventBus eventBus = EventBusFactory.getDefault().eventBus();
// Send message to specific path (Id equals 5)
eventBus.publish("5", pushMessage);
// Send global push message
eventBus.publish(pushMessage);
I've also added the following entry to my web.xml
<servlet>
<servlet-name>PrimePushServlet</servlet-name>
<servlet-class>org.primefaces.push.PushServlet</servlet-class>
<init-param>
<param-name>org.atmosphere.cpr.broadcasterCacheClass</param-name>
<param-value>org.atmosphere.cache.UUIDBroadcasterCache</param-value>
</init-param>
<init-param>
<param-name>org.atmosphere.util.IOUtils.readGetBody</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>PrimePushServlet</servlet-name>
<url-pattern>/primepush/*</url-pattern>
</servlet-mapping>
You could also take a look at the PrimeFaces Chat Example. The application uses chat rooms as path. Multiple users can be in a single chat room, so all of the users within that specific channel receive the push message.
Passing custom entities
To pass custom entities, rather than just plain Strings or FacesMessages, you have to write your own Encoder/Decoder class.
public class PushMessageDecoder implements Decoder<String, YourEntity>
{
#Override
public PushMessage decode(String s)
{
return (YourEntity) new JSONDecoder().decode(s);
}
}
public class PushMessageEncoder implements Encoder<YourEntity, String>
{
#Override
public String encode(YourEntity message)
{
return new JSONObject(message).toString();
}
}
In your push endpoint, you have to specify your custom encoder/decoder within the #OnMessage annotation.
#OnMessage(decoders = { PushMessageDecoder.class }, encoders = { PushMessageEncoder.class })
Verifying Push
You can pretty easily verify, if your push endpoint is working as expected.
Push a message to the given channel, i.e.
// 56156498494 is the mac address (not sure if special characters work in the path, you may have to encode the mac address).
eventBus.publish("/56156498494", pushMessage);
Now, simply set a break point or make a system.out in your #onMessage function in the push endpoint. You can take a look at the variable annoted with #PathParam and verify, that the value of that variable is actually the channel you wanted to push the message in.
To verify your client side handleMessage function is working and the push message is actually being retreived, simply put a alert('foo'); in your handleMessage function.
Related
I have a Service that is Producing and Consuming messages from different Spring Cloud Stream Channels (bound to EventHub/Kafka topics). There are several such Services which are setup similarly.
The configuration looks like below
public interface MessageStreams {
String WORKSPACE = "workspace";
String UPLOADNOTIFICATION = "uploadnotification";
String BLOBNOTIFICATION = "blobnotification";
String INGESTIONSTATUS = "ingestionstatusproducer";
#Input(WORKSPACE)
SubscribableChannel workspaceChannel();
#Output(UPLOADNOTIFICATION)
MessageChannel uploadNotificationChannel();
#Input(BLOBNOTIFICATION)
SubscribableChannel blobNotificationChannel();
#Output(INGESTIONSTATUS)
MessageChannel ingestionStatusChannel();
}
#EnableBinding(MessageStreams.class)
public class EventHubStreamsConfiguration {
}
The Producer/Publisher code looks like below
#Service
#Slf4j
public class IngestionStatusEventPublisher {
private final MessageStreams messageStreams;
public IngestionStatusEventPublisher(MessageStreams messageStreams) {
this.messageStreams = messageStreams;
}
public void sendIngestionStatusEvent() {
log.info("Sending ingestion status event");
System.out.println("Sending ingestion status event");
MessageChannel messageChannel = messageStreams.ingestionStatusChannel();
boolean messageSent = messageChannel.send(MessageBuilder
.withPayload(IngestionStatusMessage.builder()
.correlationId("some-correlation-id")
.status("done")
.source("some-source")
.eventTime(OffsetDateTime.now())
.build())
.setHeader("tenant-id", "some-tenant")
.build());
log.info("Ingestion status event sent successfully {}", messageSent);
}
}
Similarly I have multiple other Publishers which publish to different Event Hubs/Topics. Notice that there is a tenant-id header being set for each published message. This is something specific to my multi-tenant application to track the tenant context. Also notice that I am getting the channel to be published to while sending the message.
My Consumer code looks like below
#Component
#Slf4j
public class IngestionStatusEventHandler {
private AtomicInteger eventCount = new AtomicInteger();
#StreamListener(TestMessageStreams.INGESTIONSTATUS)
public void handleEvent(#Payload IngestionStatusMessage message, #Header(name = "tenant-id") String tenantId) throws Exception {
log.info("New ingestion status event received: {} in Consumer: {}", message, Thread.currentThread().getName());
// set the tenant context as thread local from the header.
}
Again I have several such consumers and also there is a tenant context that is set in each consumer based on the incoming tenant-id header that is sent by the Publisher.
My questions is
How do I get rid of the boiler plate code of setting the tenant-id header in Publisher and setting the tenant context in the Consumer by abstracting it into a library which could be included in all the different Services that I have.
Also, is there a way of dynamically identifying the Channel based on the Type of the Message being published. for ex IngestionStatusMessage.class in the given scenario
To set and tenant-id header in the common code and to avoid its copy/pasting in every microservice you can use a ChannelInterceptor and make it as global one with a #GlobalChannelInterceptor and its patterns option.
See more info in Spring Integration: https://docs.spring.io/spring-integration/docs/5.3.0.BUILD-SNAPSHOT/reference/html/core.html#channel-interceptors
https://docs.spring.io/spring-integration/docs/5.3.0.BUILD-SNAPSHOT/reference/html/overview.html#configuration-enable-integration
You can't make a channel selection by the payload type because the payload type is really determined from the #StreamListener method signature.
You can try to have a general #Router with a Message<?> expectation and then return a particular channel name to route according that request message context.
See https://docs.spring.io/spring-integration/docs/5.3.0.BUILD-SNAPSHOT/reference/html/message-routing.html#messaging-routing-chapter
I was just wondering to implement a kind of log display to user where in all messages in the application are displayed to user all the time.
Since I use JSF 1.2 and RF 3.3.3, wanted to know if it is possible to save all messages be it added by different requests and display them to user, so that user will know the history of actions he has done. It is also useful for support team to analyse the cause of the problem and he can send the messages to developer if it needs to simulated or to debug purpose also.
I also know facesmessages get cleared over different requests, thats where my question lies, to save messages over different requests :)
Could be handled in different ways by saving them is a session variable or so...I would appreciate all possible answers
You could collect all messages during the render response in a PhaseListener. E.g.
public class MessagesListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
Iterator<String> clientIds = context.getClientIdsWithMessages();
while (clientIds.hasNext()) {
String clientId = clientIds.next();
Iterator<FacesMessage> messages = context.getMessages(clientId);
while (messages.hasNext()) {
FacesMessage message = messages.next();
save(clientId, message); // Do your job here.
}
}
}
#Override
public void afterPhase(PhaseEvent event) {
// NOOP.
}
}
To get it to run, register it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.MessagesListener</phase-listener>
</lifecycle>
Working with JBoss AS 5.1, JSF 1.2 and Seam 2.2, I'm trying to log session openings and closings.
AFAIK, at the moment of the org.jboss.seam.preDestroyContext.SESSION event, in case of a session timeout, there's no FacesContext which seems natural as there is no running HTTP request, so I can't get the session ID from it. But there's still a Seam session context which is available by Contexts.getSession().
When I dynamically inspect the Contexts.getSession() object in a debugger, I can see the JSESSIONID in some inner Map. I would like to do something like:
String sessionId = Contexts.getSession().get("JSESSIONID");
But apparently, JSESSIONID is not the right key to retrieve the session ID. I tried id, SessionId without success. The SessionContext.getNames() method returns a list of keys:
anonPersistentPermissionResolver
loggedUserId
org.jboss.seam.security.ruleBasedPermissionResolver
org.jboss.seam.web.session
com.sun.faces.application.StateManagerImpl.SerialId
org.jboss.seam.international.timeZoneSelector
org.jboss.seam.international.localeSelector
org.jboss.seam.security.defaultResolverChain
org.jboss.seam.security.persistentPermissionResolver
javax.faces.request.charset
crumbs
org.jboss.seam.core.conversationEntries
debateId
org.jboss.seam.security.credentials
com.sun.faces.logicalViewMap
org.jboss.seam.security.identity
org.jboss.seam.security.rememberMe
The value for org.jboss.seam.web.session doesn't contain the session ID.
How do I get the session ID from Contexts.getSession()?
Maybe you can use a classic HttpSessionListener to log what you need. Put in your web.xml:
...
<listener>
<listener-class>com.yourcompany.YourSessionListener</listener-class>
</listener>
...
The listener implementation is like this:
package com.yourcompany;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.log4j.Logger;
public class YourSessionListener implements HttpSessionListener {
private static Logger log = Logger.getLogger(YourSessionListener.class);
public void sessionCreated(HttpSessionEvent event) {
log.info("creating http session: " + event.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent event) {
log.info("destroying http session: " + event.getSession().getId());
}
}
Is there some configuration or available module in Spring Security to limit login attempts (ideally, I'd like to have an increasing wait time between subsequent failed attempts)? If not, which part of the API should be used for this?
From Spring 4.2 upwards annotation based event listeners are available:
#Component
public class AuthenticationEventListener {
#EventListener
public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
// update the failed login count for the user
// ...
}
}
Implement an AuthenticationFailureHandler that updates a count/time in the DB. I wouldn't count on using the session because the attacker is not going to be sending cookies anyway.
I recently implemented a similar functionality to monitor login failures using JMX. Please see the code in my answer to question Publish JMX notifications in using Spring without NotificationPublisherAware. An aspect on the authenticate method of authentication provider updates MBean and works with a notification listener (code not shown in that question) to block user and IP, send alert emails and even suspend the login if failures exceed a threshold.
Edit
Similar to my answer to question Spring security 3 : Save informations about authentification in database, I think that capturing an authentication failure event (as opposed to customizing a handler) and storing information in database will also work and it will keep the code decoupled as well.
As suggested by Rob Winch in http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security, I just subclassed DaoAuthenticationProvider (which could also have been done using an aspect as Ritesh suggests) to limit the number of failed logins, but you could also assert pre-conditions as well:
public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Could assert pre-conditions here, e.g. rate-limiting
// and throw a custom AuthenticationException if necessary
try {
return super.authenticate(authentication);
} catch (BadCredentialsException e) {
// Will throw a custom exception if too many failed logins have occurred
userService.recordLoginFailure(authentication);
throw e;
}
}
}
In Spring config XML, simply reference this bean:
<beans id="authenticationProvider"
class="mypackage.LimitingDaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService"
p:passwordEncoder-ref="passwordEncoder"/>
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
Note that I think that solutions which rely on accessing an AuthenticationException's authentication or extraInformation properties (such as implementing an AuthenticationFailureHandler) should probably not be used because those properties are now deprecated (in Spring Security 3.1 at least).
You could also use a service which implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> to update the record in DB.
See spring application events.
Here is my implementation, hope help.
Create a table to store any invalid login attempts.
If invalid attempts > max allowed, set UserDetail.accountNonLocked to false
Spring Security will handle the "lock process" for you. (refer to AbstractUserDetailsAuthenticationProvider)
Last, extends DaoAuthenticationProvider, and integrate the logic inside.
#Component("authenticationProvider")
public class YourAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
UserAttemptsDao userAttemptsDao;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
Authentication auth = super.authenticate(authentication);
//if corrent password, reset the user_attempts
userAttemptsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//invalid login, update user_attempts, set attempts+1
userAttemptsDao.updateFailAttempts(authentication.getName());
throw e;
}
}
}
For full source code and implementation, please refer to this - Spring Security limit login attempts example,
create a table to store the values of failed attempts ex : user_attempts
Write custom event listener
#Component("authenticationEventListner")
public class AuthenticationEventListener
implements AuthenticationEventPublisher
{
#Autowired
UserAttemptsServices userAttemptsService;
#Autowired
UserService userService;
private static final int MAX_ATTEMPTS = 3;
static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
#Override
public void publishAuthenticationSuccess(Authentication authentication) {
logger.info("User has been logged in Successfully :" +authentication.getName());
userAttemptsService.resetFailAttempts(authentication.getName());
}
#Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
logger.info("User Login failed :" +authentication.getName());
String username = authentication.getName().toString();
UserAttempts userAttempt = userAttemptsService.getUserAttempts(username);
User userExists = userService.findBySSO(username);
int attempts = 0;
String error = "";
String lastAttempted = "";
if (userAttempt == null) {
if(userExists !=null ){
userAttemptsService.insertFailAttempts(username); }
} else {
attempts = userAttempt.getAttempts();
lastAttempted = userAttempt.getLastModified();
userAttemptsService.updateFailAttempts(username, attempts);
if (attempts + 1 >= MAX_ATTEMPTS) {
error = "User account is locked! <br>Username : "
+ username+ "<br>Last Attempted on : " + lastAttempted;
throw new LockedException(error);
}
}
throw new BadCredentialsException("Invalid User Name and Password");
}
}
3.Security Configuration
1) #Autowired
#Qualifier("authenticationEventListner")
AuthenticationEventListener authenticationEventListner;
2) #Bean
public AuthenticationEventPublisher authenticationListener() {
return new AuthenticationEventListener();
}
3) #Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//configuring custom user details service
auth.authenticationProvider(authenticationProvider);
// configuring login success and failure event listener
auth.authenticationEventPublisher(authenticationEventListner);
}
I'm facing a little issue with Spring Security 3.0.x (3.0.2 in particular at the moment). The whole application I'm working on is working perfectly except when someone who doesn't have the authorities tries to log on.
When it occurs, the users is redirected to the "welcome" page, since his username/password are valid, and he receive a cute white page with this : "Error 403: Access is denied"
So, I've been looking on the net trying to find how this behavior can be handled. So far I've come to the conclusion, please correct me if I'm wrong, that it is managed by the ExceptionTranslationFilter. But I don't quite understand how to make any good use of this information.
I've tryied to edit my SecurityContext.xml to add a access-denied-handler tag to my http tag, but it doesn't work. Do I need to add more than this tag to make it work? Is there any other possibilities to make my application more user-friendly?
Edit : I would like to redirect to a page, let's says 403.html, for example.
Sincerly,
Thanks
I still don't get why you had to implement your own access handler... I have currently faced same task:
<security:access-denied-handler error-page="/accessDenied"/> - works like charm.
Don't forget to specify handler in your Controller:
#RequestMapping(value = "/accessDenied")
public String accessDenied() {
return "accessDenied"; // logical view name
}
Update for Spring Boot(2014 Oct):
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(customHandler) OR .accessDeniedPage("/somePage.html").and
.formLogin()
.failureHandler(ajaxAuthenticationFailureHandler)}
Nowadays we don't really return views for such task since angular js kicks in so you can use your failure/success handler and return tailored JSON responses. For us it was sufficient to use failure handler but you get to choose where you want your control to kick in. We generally don't use view resolvers as there are UI tiles frameworks(such as angular partials) able to construct pieces into single page for you. Html pieces are stored on the server and served simply as static resources.
Lets play with Embedded Tomcat to achieve similar behavior to web.xml !
#Configuration
#EnableAutoConfiguration
public class ApplicationWebXml extends SpringBootServletInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.profiles(addDefaultProfile())
.showBanner(false)
.sources(Application.class);
}
//required for container customizer to work, the numerous tutorials didn't work for me, so I simply tried overriding the default one
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
return tomcat;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(
) {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;
containerFactory.setSessionTimeout(1); // just for your interest, remove as necessary
containerFactory.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN,"/views/accessDenied.html"),
new ErrorPage(HttpStatus.NOT_FOUND,"/views/notFound.html"));
containerFactory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setPort(8082);// just for your interest, remove as necessary
}
});
}
};
}
}
A cleaner way to handle error redirects is to use the <error-page> and <error-code> tags in your web.xml. See below for an example:
<!-- Custom 403 Error Page -->
<!--
NOTE: Security will throw this error when a user has been authenticated successfully
but lacks the permissions to perform the requested action.
-->
<error-page>
<error-code>403</error-code>
<location>/403.jsp</location>
</error-page>
This block of code will redirect to the specified location whenever it encounters the specified error code.
This eliminates the need for authorization code inside your application logic.
I've found how to do this. By implementing the AccessDeniedHandler interface and the corresponding handle method I can, easily, control the way the Http 403 error is handled.
This way, you can add various items in the session and then intercept them on your jsp.
The xml file then looks like this :
<sec:http>
<!-- lots of urls here -->
<sec:access-denied-handler ref="accessDeniedHandler" />
<sec:anonymous/>
</sec:http>
<bean id="accessDeniedHandler" class="foo.bar.CustomAccessDeniedHandler">
<property name="accessDeniedUrl" value="403.html" />
</bean>
The java class :
package foo.bar;
public class CustomAccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
private String accessDeniedUrl;
public CustomAccessDeniedHandler() {
}
public CustomAccessDeniedHandler(String accessDeniedUrl) {
this.accessDeniedUrl = accessDeniedUrl;
}
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect(accessDeniedUrl);
request.getSession().setAttribute("CustomSessionAttribute", "value here");
}
public String getAccessDeniedUrl() {
return accessDeniedUrl;
}
public void setAccessDeniedUrl(String accessDeniedUrl) {
this.accessDeniedUrl = accessDeniedUrl;
}
}
And a jsp example :
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:if test="${!empty CustomSessionAttribute}">
<br/>
ACCESS IS DENIED
<br/>
</c:if>
<!-- other stuff down here -->
The way to make this work is to define a handler in your entry point:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
if (authException != null) {
// you can check for the spefic exception here and redirect like this
response.sendRedirect("403.html");
}
}
}
You can define this as your entry point by setting this as you entry point in the xml config file:
<http entry-point-ref="customAuthenticationEntryPoint">
...
</http>
You have checked the tag in an application and to me it seems to work.
<sec:access-denied-handler error-page="/handle403Url" />
where handle403Url I want to call to handle this error (for example to show an error).
Don't forget that you have to allow this url in the filters so it can be reached by this user authority, so in the start of the flters you have to add something like this:
<sec:intercept-url pattern="/handle403Url" filters="none" />