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());
}
}
Related
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.
i am learning JMS, i want to apply it with JSF, i created 2 managed beans the producer which sends the message and the consumer, i made a MDB which implements the messagelistner and has onMessage method, the JMS provider is Wildfly 8, i can send the message from JSF by the producer and the MDB receive it, i want to make the MDB access the consumer managed bean and set the message then update it in the JSF again, i tried #Managedproberty and #EJB but i get null, also the requestContext and faceContext is null in the MDB, i am still beginner at EJB so i miss many things, my question how can i access the managed bean from EJB generally and MDB specially, i know i can use other technologies to do that like prime push or even web sockets but i want to learn the JSM first.
this is the MDB code
package boddooo.jms;
import java.io.Serializable;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.primefaces.context.RequestContext;
#MessageDriven(
activationConfig = { #ActivationConfigProperty(
propertyName = "destination", propertyValue = "jms/queue/boddooo"), #ActivationConfigProperty(
propertyName = "destinationType", propertyValue = "javax.jms.Queue")
},
mappedName = "jms/queue/boddooo")
public class mdb implements Serializable, MessageListener {
private static final long serialVersionUID = 1L;
TextMessage tm;
#Override
public void onMessage(Message message) {
if(RequestContext.getCurrentInstance()!=null){
RequestContext fc=RequestContext.getCurrentInstance();
consumer c=(consumer)fc.getAttributes().get("consumer");
tm = (TextMessage) message;
try {
c.setMsg(tm.getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
I just added a Websocket endpoint to my java ee jax-rs application. Within Jax-Rs endpoints i can access the role of the user via SecurityContext.
But within websocket i can't inject context stuff. So how to know the role of the user that tries to open a websocket session?
For this you will have to modify the Websocket handshake. You can do this as below:
1) Modify you websocket endpoint to use custom configurator
#ServerEndpoint(value = "/someWSEndpoint", configurator = SomeCustomConfigurationClass.class)
public class SomeWSService {
...
}
2) Modify WS Handshake similar to
public class SomeCustomConfigurationClass extends ServerEndpointConfig.Configurator {
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
config.getUserProperties().put("UserPrincipal",request.getUserPrincipal());
config.getUserProperties().put("userInRole", request.isUserInRole("someRole"));
}
}
3) Now you can access this in you ws endpoint class as
#OnOpen
public void onOpen(final Session session, EndpointConfig config) {
Principal userPrincipal = (Principal) config.getUserProperties().get("UserPrincipal");
Boolean userInRole = (Boolean) config.getUserProperties().get("userInRole");
//do what ever you like with it
}
Good evening,
In a test JSF 2.0 web app, I am trying to get the number of active sessions but there is a problem in the sessionDestroyed method of the HttpSessionListener.
Indeed, when a user logs in, the number of active session increases by 1, but when a user logs off, the same number remains as it is (no desincrementation happens) and the worse is that, when the same user logs in again (even though he unvalidated the session), the same number is incremented.
To put that in different words :
1- I log in, the active sessions number is incremented by 1.
2- I Logout (the session gets unvalidated)
3- I login again, the sessions number is incremented by 1. The display is = 2.
4- I repeat the operation, and the sessions number keeps being incremented, while there is only one user logged in.
So I thought that method sessionDestroyed is not properly called, or maybe effectively called after the session timeout which is a parameter in WEB.XML (mine is 60 minutes).
That is weird as this is a Session Listener and there is nothing wrong with my Class.
Does someone please have a clue?
package mybeans;
import entities.Users;
import java.io.*;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import jsf.util.JsfUtil;
/**
* Session Listener.
* #author TOTO
*/
#ManagedBean
public class SessionEar implements HttpSessionListener {
public String ctext;
File file = new File("sessionlog.csv");
BufferedWriter output = null;
public static int activesessions = 0;
public static long creationTime = 0;
public static int remTime = 0;
String separator = ",";
String headtext = "Session Creation Time" + separator + "Session Destruction Time" + separator + "User";
/**
*
* #return Remnant session time
*/
public static int getRemTime() {
return remTime;
}
/**
*
* #return Session creation time
*/
public static long getCreationTime() {
return creationTime;
}
/**
*
* #return System time
*/
private String getTime() {
return new Date(System.currentTimeMillis()).toString();
}
/**
*
* #return active sessions number
*/
public static int getActivesessions() {
return activesessions;
}
#Override
public void sessionCreated(HttpSessionEvent hse) {
// Insert value of remnant session time
remTime = hse.getSession().getMaxInactiveInterval();
// Insert value of Session creation time (in seconds)
creationTime = new Date(hse.getSession().getCreationTime()).getTime() / 1000;
if (hse.getSession().isNew()) {
activesessions++;
} // Increment the session number
System.out.println("Session Created at: " + getTime());
// We write into a file information about the session created
ctext = String.valueOf(new Date(hse.getSession().getCreationTime()) + separator);
String userstring = FacesContext.getCurrentInstance().getExternalContext().getRemoteUser();
// If the file does not exist, create it
try {
if (!file.exists()) {
file.createNewFile();
output = new BufferedWriter(new FileWriter(file.getName(), true));
// output.newLine();
output.write(headtext);
output.flush();
output.close();
}
output = new BufferedWriter(new FileWriter(file.getName(), true));
//output.newLine();
output.write(ctext + userstring);
output.flush();
output.close();
} catch (IOException ex) {
Logger.getLogger(SessionEar.class.getName()).log(Level.SEVERE, null, ex);
JsfUtil.addErrorMessage(ex, "Cannot append session Info to File");
}
System.out.println("Session File has been written to sessionlog.txt");
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
// Desincrement the active sessions number
activesessions--;
// Appen Infos about session destruction into CSV FILE
String stext = "\n" + new Date(se.getSession().getCreationTime()) + separator;
try {
if (!file.exists()) {
file.createNewFile();
output = new BufferedWriter(new FileWriter(file.getName(), true));
// output.newLine();
output.write(headtext);
output.flush();
output.close();
}
output = new BufferedWriter(new FileWriter(file.getName(), true));
// output.newLine();
output.write(stext);
output.flush();
output.close();
} catch (IOException ex) {
Logger.getLogger(SessionEar.class.getName()).log(Level.SEVERE, null, ex);
JsfUtil.addErrorMessage(ex, "Cannot append session Info to File");
}
}
} // END OF CLASS
I am retrieving the active sessions number this way:
<h:outputText id="sessionsfacet" value="#{UserBean.activeSessionsNumber}"/>
from another managedBean:
public String getActiveSessionsNumber() {
return String.valueOf(SessionEar.getActivesessions());
}
My logout method is as follow:
public String logout() {
HttpSession lsession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
if (lsession != null) {
lsession.invalidate();
}
JsfUtil.addSuccessMessage("You are now logged out.");
return "Logout";
}
// end of logout
I'm not sure. This seems to work fine for a single visitor. But some things definitely doesn't look right in your HttpSessionListener.
#ManagedBean
public class SessionEar implements HttpSessionListener {
Why is it a #ManagedBean? It makes no sense, remove it. In Java EE 6 you'd use #WebListener instead.
BufferedWriter output = null;
This should definitely not be an instance variable. It's not threadsafe. Declare it methodlocal. For every HttpSessionListener implementation there's only one instance throughout the application's lifetime. When there are simultaneous session creations/destroys, then your output get overridden by another one while busy and your file would get corrupted.
public static long creationTime = 0;
public static int remTime = 0;
Those should also not be an instance variable. Every new session creation would override it and it would get reflected into the presentation of all other users. I.e. it is not threadsafe. Get rid of them and make use of #{session.creationTime} and #{session.maxInactiveInterval} in EL if you need to get it over there for some reason. Or just get it straight from the HttpSession instance within a HTTP request.
if (hse.getSession().isNew()) {
This is always true inside sessionCreated() method. This makes no sense. Remove it.
JsfUtil.addErrorMessage(ex, "Cannot append session Info to File");
I don't know what that method exactly is doing, but I just want to warn that there is no guarantee that the FacesContext is present in the thread when the session is about to be created or destroyed. It may take place in a non-JSF request. Or there may be no means of a HTTP request at all. So you risk NPE's because the FacesContext is null then.
Nonetheless, I created the following test snippet and it works fine for me. The #SessionScoped bean implicitly creates the session. The commandbutton invalidates the session. All methods are called as expected. How many times you also press the button in the same browser tab, the count is always 1.
<h:form>
<h:commandButton value="logout" action="#{bean.logout}" />
<h:outputText value="#{bean.sessionCount}" />
</h:form>
with
#ManagedBean
#SessionScoped
public class Bean implements Serializable {
public void logout() {
System.out.println("logout action invoked");
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
}
public int getSessionCount() {
System.out.println("session count getter invoked");
return SessionCounter.getCount();
}
}
and
#WebListener
public class SessionCounter implements HttpSessionListener {
private static int count;
#Override
public void sessionCreated(HttpSessionEvent event) {
System.out.println("session created: " + event.getSession().getId());
count++;
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("session destroyed: " + event.getSession().getId());
count--;
}
public static int getCount() {
return count;
}
}
(note on Java EE 5 you need to register it as <listener> in web.xml the usual way)
<listener>
<listener-class>com.example.SessionCounter</listener-class>
</listener>
If the above example works for you, then your problem likely lies somewhere else. Perhaps you didn't register it as <listener> in web.xml at all and you're simply manually creating a new instance of the listener everytime inside some login method. Regardless, now you at least have a minimum kickoff example to build further on.
Something in a completely different direction - tomcat supports JMX. There is a JMX MBean that will tell you the number of active sessions. (If your container is not tomcat, it should still support JMX and provide some way to track that)
Is your public void sessionDestroyed(HttpSessionEvent se) { called ? I don't see why it won't increment. After the user calls session.invalidate() through logout, the session is destroyed, and for the next request a new one is created. This is normal behavior.
I've tested the default security containers in Glassfish 3.0.1 and come to the conclusion that I won't spend any more time on that. Instead I want to control the verification myself. But I need some guidance to get me on right track.
At the moment I have a UserBean that has a login/logout function (see below). And I don't want to use the *j_security_check* built in container, but use core JSF 2.0.
My questions are;
Do I need a ServletFilter to redirect traffic if the user is not logged in (if accessing certain folders)?
How do I store User Pricipals after the user successfully logged in ?
Appreciate any help or link to a example, greetings Chris.
PS. Excuse me for clustering two questions together
#ManagedBean
#SessionScoped
public class UserBean {
private AuthenticateUser authenticateUser;
...
public String login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
JsfUtil.log("Username : " +authenticateUser.getUserName());
JsfUtil.log("Password : " +authenticateUser.getPassword());
AuthenticateUser authRequest = authenticationFacade.find(authenticateUser);
try {
if(!authRequest.equals(authenticateUser))
return "/loginError";
request.login(authenticateUser.getUserName(), authenticateUser.getPassword());
return "";
} catch(ServletException e){
JsfUtil.addErrorMessage(e, "Incorrect username or password, please try again.");
return "/loginError";
}
...
public String logOut() {
String result = "/index?faces-redirect=true";
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
request.logout();
} catch (ServletException e) {
JsfUtil.log("Failed to logout user!" +e.getRootCause().toString());
result = "/loginError?faces-redirect=true";
}
return result;
}
When you want to utilize request.login(), then you should really have configured a Realm in the container which represents the user database. But you seem to have replaced the Realm by some AuthenticationFacade. In this case, the request.login() is not useful for you.
You need to just put the user in the session scope and intercept on that. Here's a kickoff example:
#ManagedBean
#SessionScoped
public class UserManager {
#EJB
private UserService userService;
private String username;
private String password;
private User current;
public String login() {
current = userService.find(username, password);
if (current == null) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Unknown login, try again"));
return null;
} else {
return "userhome?faces-redirect=true";
}
}
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "index?faces-redirect=true";
}
public boolean isLoggedIn() {
return current != null;
}
// Getters/setters (but do NOT provide a setter for current!)
}
When taking authentication in hands like this, then you definitely need a filter to restrict access. When using container managed security you would typically specify it as <url-pattern> of <security-constraint> for this. But without it, you've to take it in your hands. It's good to know that JSF managed beans are keyed by their managed bean name in any scope.
UserManager userManager = ((HttpServletRequest) request).getSession().getAttribute("userManager");
if (userManager == null || !userManager.isLoggedIn()) {
((HttpServletResponse) response).sendRedirect("login.xhtml");
} else {
chain.doFilter(request, response);
}
Map the above filter on the desired URL-pattern.
When you still want to reconsider using container managed authentication, then the following related answers may be useful:
Java EE Login Page Problem (and Configuring Realm in Glassfish)
Performing user authentication in Java EE / JSF using j_security_check
Be aware if you are if you are using JDBC realm security. There are some fixed/expected words in the fields where you configure the realm in the Glassfish admin console.
In the JAAS Context: filed, you have to type: jdbcRealm. This keyword makes the security container use the expected JDBC realm. If you type something else, it won't work.
Here is good example, done by Gordan Jugo; Netbeans/Glassfish JDBC Security Realm