带有 CDI 的 JSF 2.3 中的会话固定和会话作用域 Bean
Session Fixation and Session Scoped Beans in JSF 2.3 with CDI
在用户登录时更新 HTTP 会话是一种常见的最佳做法。这将强制使用新的会话 ID,避免会话固定漏洞。
当涉及@SessionScoped bean 时,是否有使用 CDI 实现此功能的首选模式?困难在于,通过使当前 HTTP 会话无效,您将在下一个请求中获得一个不同的会话范围的 bean,但直到下一个请求。
例如,假设一个用于存储用户登录信息的会话bean:
@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
private int userId;
private String username;
private List<String> privileges;
// Accessors omitted
}
还有另一个用于管理登录的 bean:
@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;
}
}
对于托管 Bean,这将检索一个新的 SessionBean,但是对于 CDI,上面的代码只会 return 相同的 SessionBean。有什么建议或妙招吗?
由于我不是安全专家,所以我将把这个答案限制为仅与 CDI 有关。我也不知道被要求的一般事情是否是个好主意。无论如何,这就是我认为你会按照你的要求去做的方式。
用纯粹的 CDI 术语表达,问题可以改写为:
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?
一般方法是:
@Inject
一个 Provider<SessionBean>
而不是直接 SessionBean
(这会让你正确地向 CDI 请求 "new" 对象)
@Inject
一个BeanManager
(这样你就可以获得管理SessionScoped
对象的权利Context
)
- 求
BeanManager
给你AlterableContext
对应的SessionScoped
注解
- 告诉
AlterableContext
销毁当前 bean 的上下文实例
- 调用
Provider.get()
创建一个新的
因此,您的 doLogin
方法的相关部分可能如下所示(未经测试):
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;
我认为应该可行。
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.
那就不要让session失效,而是改变session ID。换句话说,不要使用 HttpSession#invalidate()
, but use HttpServletRequest#changeSessionId()
(自 Servlet 3.1 以来的新功能,鉴于您使用的是 JSF 2.3,您无疑应该已经在使用它)。
在代码中,替换
// force renewal of HTTP session object
context.getExternalContext().invalidateSession();
来自
// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();
这基本上改变了 JSESSIONID
cookie 而没有改变 HttpSession
。它非常适合防止会话固定。
显式使会话无效通常仅在注销期间有用。
在用户登录时更新 HTTP 会话是一种常见的最佳做法。这将强制使用新的会话 ID,避免会话固定漏洞。
当涉及@SessionScoped bean 时,是否有使用 CDI 实现此功能的首选模式?困难在于,通过使当前 HTTP 会话无效,您将在下一个请求中获得一个不同的会话范围的 bean,但直到下一个请求。
例如,假设一个用于存储用户登录信息的会话bean:
@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
private int userId;
private String username;
private List<String> privileges;
// Accessors omitted
}
还有另一个用于管理登录的 bean:
@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;
}
}
对于托管 Bean,这将检索一个新的 SessionBean,但是对于 CDI,上面的代码只会 return 相同的 SessionBean。有什么建议或妙招吗?
由于我不是安全专家,所以我将把这个答案限制为仅与 CDI 有关。我也不知道被要求的一般事情是否是个好主意。无论如何,这就是我认为你会按照你的要求去做的方式。
用纯粹的 CDI 术语表达,问题可以改写为:
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?
一般方法是:
@Inject
一个Provider<SessionBean>
而不是直接SessionBean
(这会让你正确地向 CDI 请求 "new" 对象)@Inject
一个BeanManager
(这样你就可以获得管理SessionScoped
对象的权利Context
)- 求
BeanManager
给你AlterableContext
对应的SessionScoped
注解 - 告诉
AlterableContext
销毁当前 bean 的上下文实例 - 调用
Provider.get()
创建一个新的
因此,您的 doLogin
方法的相关部分可能如下所示(未经测试):
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;
我认为应该可行。
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.
那就不要让session失效,而是改变session ID。换句话说,不要使用 HttpSession#invalidate()
, but use HttpServletRequest#changeSessionId()
(自 Servlet 3.1 以来的新功能,鉴于您使用的是 JSF 2.3,您无疑应该已经在使用它)。
在代码中,替换
// force renewal of HTTP session object
context.getExternalContext().invalidateSession();
来自
// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();
这基本上改变了 JSESSIONID
cookie 而没有改变 HttpSession
。它非常适合防止会话固定。
显式使会话无效通常仅在注销期间有用。