问题
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?
回答1:
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.
回答2:
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 thisContext
. How can I properly tell theContext
to invalidate the current object that it is managing, and load or create a new one?
The general approach is going to be:
@Inject
aProvider<SessionBean>
instead ofSessionBean
directly (this will let you ask CDI for the "new" object properly)@Inject
aBeanManager
(so you can get the rightContext
that managesSessionScoped
objects)- ask the
BeanManager
to give you theAlterableContext
corresponding to theSessionScoped
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.
来源:https://stackoverflow.com/questions/59667297/session-fixation-and-session-scoped-beans-in-jsf-2-3-with-cdi