Java EE身份validation:如何捕获登录事件?

给定为Java Web应用程序定义的FORM类型的身份validation机制,如何在重定向到请求的资源之前捕获登录执行的事件? 是否有任何类型的监听器,我可以在用户登录时将我的代码执行?

我觉得定义filter不是最好的解决方案,因为filter链接到资源,即使用户已经过身份validation并要求资源,也会调用filter。 我想知道是否只有登录事件触发了一些类/方法。

Java EE中没有这样的事件。 然而。 作为JSR375的一部分,容器管理的安全性将完全重做,因为它目前分散在不同的容器实现中,并且不是跨容器兼容的。 这在Java EE 8 Security API演示文稿中有概述。

已经有一个正在进行的安全API的参考实现, Soteria ,由我的同事Arjan Tijms开发。 使用新的安全API,CDI将用于触发您只能@Observes身份validation事件。 关于规范的讨论发生在这个邮件列表线程中 。 它尚未在Soteria中具体实施。

在此之前,假设基于FORM的身份validation(其中用户主体内部存储在会话中),最好的办法是在servletfilter中手动检查,如果请求中存在用户主体,而您的登录用户的表示不存在HTTP会话。

 @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; String username = request.getRemoteUser(); if (username != null && request.getSession().getAttribute("user") == null) { // First-time login. You can do your thing here. User user = yourUserService.find(username); request.getSession().setAttribute("user", user); } chain.doFilter(req, res); } 

请注意,在/j_security_check上注册filter不能保证工作,因为一个像样的容器会在第一个filter被命中之前在内部处理它,出于明显的安全原因(用户提供的filter可能会以不正当的方式操纵请求,无论是意外还是awarely)。

如果您碰巧使用Java EE服务器使用Undertow servletcontainer,例如WildFly ,那么有一种更简洁的方法可以挂钩其内部通知事件,然后触发自定义CDI事件。 这是在Arjan Tijms的博客中充实的。 如博客所示,您最终可以得到这样的CDI bean:

 @SessionScoped public class SessionAuthListener implements Serializable { private static final long serialVersionUID = 1L; public void onAuthenticated(@Observes AuthenticatedEvent event) { String username = event.getUserPrincipal().getName(); // Do something with name, eg audit, // load User instance into session, etc } public void onLoggedOut(@Observes LoggedOutEvent event) { // take some action, eg audit, null out User, etc } } 

您可以在j_security_check URI上使用Servletfilter。 不会在每个请求上调用此filter,而是仅在登录请求上调用。

查看以下页面 – 开发用于表单登录处理的servletfilter – 这适用于WebSphere App Server和WebSphere Liberty概要文件。

有这样的filter:

 @WebFilter("/j_security_check") public class LoginFilter implements Filter { ... public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter called 1: " +((HttpServletRequest)request).getUserPrincipal()); chain.doFilter(request, response); System.out.println("Filter called 2: " + ((HttpServletRequest)request).getUserPrincipal()); } 

给出以下输出:

 // on incorrect login Filter called 1: null [AUDIT ] CWWKS1100A: Authentication did not succeed for user ID user1. An invalid user ID or password was specified. Filter called 2: null // on correct login Filter called 1: null Filter called 2: WSPrincipal:user1 

UPDATE

其他可能的方法是使用您自己的servlet进行登录,将登录页面中的操作更改为该servlet并使用request.login()方法。 这是servlet API,因此即使在Wildfly中也可以工作,并且您可以完全控制登录。 您只需要了解wildfly如何传递最初请求的资源URL(WebSphere通过cookie完成它)。

Servlet伪代码:

 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String user = request.getParameter("j_username"); String password = request.getParameter("j_password"); try { request.login(user, password); // redirect to requested resource } catch (Exception e) { // login failed - redirect to error login page }