简体   繁体   中英

Session Fixation and Session Scoped Beans in JSF 2.3 with CDI

It's a common best practice to renew the HTTP session when logging in a user. This will force a new session ID, avoiding session fixation vulnerabilities.

Is there a preferred pattern for implementing this with CDI when @SessionScoped beans are involved? The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

For example, assume a session bean for storing user login information:

@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
    private int userId;
    private String username;
    private List<String> privileges;

    // Accessors omitted 
}

And another bean for managing the login:

@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {

    private String username;
    private String password;
    @Inject private SessionBean session;
    @Inject private SessionManager sessionManager;
    @Inject private PrivilegeManager privilegeManager;      

    public String doLogin() {
        String destinationUrl;

        if (validate(username, password)) {
            FacesContext context = FacesContext.getCurrentInstance();

            // force renewal of HTTP session
            context.getExternalContext().invalidateSession();

            // retrieve new session bean  ** No longer works with CDI **
            Application app = context.getApplication();
            session = app.evaluateExpressionGet(context, "#{sessionbean}", SessionBean.class);

            session.setUsername(username);
            session.setSessionId(sessionManager.createNewSession(username));
            session.setPrivileges(privilegeManager.getPrivileges(username));

            destinationUrl = createLandingPageUrl();

        } else {
            destinationUrl = createFailureUrl("Unknown user or password");
        }

        return destinationUrl;
    }
}

With Managed Beans this would retrieve a new SessionBean, but with CDI, the code above would just return the same SessionBean. Any recommendations or clever ideas?

The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

Then don't invalidate the session, but change the session ID. In other words, don't use HttpSession#invalidate() , but use HttpServletRequest#changeSessionId() (new since Servlet 3.1, which you should undoubtedly already be using given that you're using JSF 2.3).

In code, replace

// force renewal of HTTP session object
context.getExternalContext().invalidateSession();

by

// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();

This basically changes the JSESSIONID cookie without changing the HttpSession . It's perfect for session fixation prevention.

Explicitly invalidating the session is usually only useful during logout.

I'm going to restrict this answer to be solely about CDI since I am not a security expert. I also don't know whether the general thing being asked for is a good idea or not. Regardless, here is how I think you would do what you're asking for.

Expressed in purely CDI terms, the question can be rephrased like:

I have an object that I know came from a particular Context . I know the lifecycle of objects produced by this Context . How can I properly tell the Context to invalidate the current object that it is managing, and load or create a new one?

The general approach is going to be:

  • @Inject a Provider<SessionBean> instead of SessionBean directly (this will let you ask CDI for the "new" object properly)
  • @Inject a BeanManager (so you can get the right Context that manages SessionScoped objects)
  • ask the BeanManager to give you the AlterableContext corresponding to the SessionScoped annotation
  • tell the AlterableContext to destroy the current bean's contextual instance
  • call Provider.get() to cause a new one to be created

So the relevant parts of your doLogin method might look like this (untested):

final AlterableContext context = (AlterableContext) this.beanManager.getContext(SessionScoped.class);
assert context != null;

final Bean<?> bean = beanManager.resolve(beanManager.getBeans(SessionBean.class));
assert bean != null;

context.destroy(bean);

final SessionBean newSessionBean = this.sessionBeanProvider.get();
assert newSessionBean != null;

I think that should work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM