使用j_security_check在Java EE / JSF中执行用户authentication

我想知道当前使用JSF 2.0(以及是否存在任何组件)和Java EE 6核心机制(login/检查权限/注销)的Web应用程序的用户身份validation的当前方法,其中包含用户信息保存在JPA实体。 Oracle Java EE教程有点稀疏(只处理servlet)。

这并没有使用像Spring-Security(acegi)或Seam这样的其他框架,但是如果可能的话,尽量使用新的Java EE 6平台(web profile)。

在searchWeb并尝试许多不同的方法之后,下面是我对Java EE 6authentication的build议:

build立安全领域:

在我的情况下,我有数据库中的用户。 所以我跟着这个博客文章创build了一个JDBC领域,可以根据我的数据库表中的用户名和MD5哈希密码对用户进行身份validation:

http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html

注意:这篇文章讨论数据库中的用户和组表。 我有一个用户类的UserType枚举属性通过javax.persistence注释映射到数据库。 我为用户和组configuration了相同的表的领域,使用userType列作为组列,它工作正常。

使用表单authentication:

继续上面的博客文章,configuration你的web.xml和sun-web.xml,而不是使用BASICauthentication,使用FORM(实际上,你使用哪一个并不重要,但是我最终使用了FORM)。 使用标准的HTML,而不是JSF。

然后使用BalusC的上面的提示,从数据库中初始化用户信息。 他build议在一个托pipe的bean从面部上下文中获得委托人。 我用一个有状态会话bean来存储每个用户的会话信息,所以我注入了会话上下文:

@Resource private SessionContext sessionContext; 

通过主体,我可以检查用户名,并使用EJB Entity Manager从数据库中获取用户信息,并存储在我的SessionInformation EJB中。

登出:

我也四处寻找注销的最佳方式。 我发现的最好的一个是使用Servlet:

  @WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"}) public class LogoutServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(false); // Destroys the session for this user. if (session != null) session.invalidate(); // Redirects back to the initial page. response.sendRedirect(request.getContextPath()); } } 

尽pipe考虑到问题的date,我的答案确实晚了,但我希望这能帮助其他人,就像我一样,最终从谷歌来到这里。

再见,

VítorSouza

我想你想使用部署描述符和j_security_check 基于表单的身份validation 。

您也可以通过使用本教程中演示的相同的预定义字段名称j_usernamej_password ,在JSF中执行此操作。

例如

 <form action="j_security_check" method="post"> <h:outputLabel for="j_username" value="Username" /> <h:inputText id="j_username" /> <br /> <h:outputLabel for="j_password" value="Password" /> <h:inputSecret id="j_password" /> <br /> <h:commandButton value="Login" /> </form> 

您可以在User getter中进行延迟加载,以检查User是否已经login,如果不是,请检查请求中是否存在Principal ,如果是,则获取与j_username关联的User

 package com.stackoverflow.q2206911; import java.io.IOException; import java.security.Principal; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.context.FacesContext; @ManagedBean @SessionScoped public class Auth { private User user; // The JPA entity. @EJB private UserService userService; public User getUser() { if (user == null) { Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal(); if (principal != null) { user = userService.find(principal.getName()); // Find User by j_username. } } return user; } } 

User明显可以通过#{auth.user}在JSF EL中访问。

要注销做一个HttpServletRequest#logout() (并将User设置为空!)。 您可以通过ExternalContext#getRequest()获得JSF中HttpServletRequest的句柄。 您也可以完全使会话无效。

 public String logout() { FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); return "login?faces-redirect=true"; } 

对于剩余(在部署描述符和领域中定义用户,angular色和约束),只需按照常规方法遵循Java EE 6教程和servletcontainer文档即可。


更新 :你也可以使用新的Servlet 3.0 HttpServletRequest#login()来做一个程序化的login,而不是使用j_security_check ,而j_security_check在一些j_security_check中可能不能被调度器访问。 在这种情况下,您可以使用完整的JSF表单和一个具有usernamepassword属性以及类似如下所示的login方法的bean:

 <h:form> <h:outputLabel for="username" value="Username" /> <h:inputText id="username" value="#{auth.username}" required="true" /> <h:message for="username" /> <br /> <h:outputLabel for="password" value="Password" /> <h:inputSecret id="password" value="#{auth.password}" required="true" /> <h:message for="password" /> <br /> <h:commandButton value="Login" action="#{auth.login}" /> <h:messages globalOnly="true" /> </h:form> 

这个视图范围的托pipebean也记得最初请求的页面:

 @ManagedBean @ViewScoped public class Auth { private String username; private String password; private String originalURL; @PostConstruct public void init() { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); originalURL = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI); if (originalURL == null) { originalURL = externalContext.getRequestContextPath() + "/home.xhtml"; } else { String originalQuery = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_QUERY_STRING); if (originalQuery != null) { originalURL += "?" + originalQuery; } } } @EJB private UserService userService; public void login() throws IOException { FacesContext context = FacesContext.getCurrentInstance(); ExternalContext externalContext = context.getExternalContext(); HttpServletRequest request = (HttpServletRequest) externalContext.getRequest(); try { request.login(username, password); User user = userService.find(username, password); externalContext.getSessionMap().put("user", user); externalContext.redirect(originalURL); } catch (ServletException e) { // Handle unknown username/password in request.login(). context.addMessage(null, new FacesMessage("Unknown login")); } } public void logout() throws IOException { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.invalidateSession(); externalContext.redirect(externalContext.getRequestContextPath() + "/login.xhtml"); } // Getters/setters for username and password. } 

通过这种方式, User可以通过#{user}在JSF EL中访问。

应该提到的是,将身份validation问题完全留给前端控制器(例如,Apache Web服务器),并评估HttpServletRequest.getRemoteUser()(这是REMOTE_USER环境variables的JAVA表示)是一个选项。 这也允许复杂的logindevise,如​​Shibbolethauthentication。 过滤通过Web服务器对servlet容器的请求是生产环境的一个很好的devise,通常使用mod_jk来实现。

HttpServletRequest.login没有在会话中设置身份validation状态的问题已经在3.0.1中修复了。 更新glassfish到最新版本,你就完成了。

更新非常简单:

 glassfishv3/bin/pkg set-authority -P dev.glassfish.org glassfishv3/bin/pkg image-update