绑定用户实体和GlassFish Principal

我有一个实体类User ,其中包含用户名,名字,姓氏和密码等信息,我有我的GlassFish 3.1服务器设置来执行身份validation。 到现在为止还挺好。 在容器validation用户之后,我需要一些方法将主体绑定到实际的User实体。 毕竟,GlassFish告诉我用户“laurens”已经过身份validation,它没有给我相应的User实体。

为此,我编写了一个JSF托管bean UserController 。 我想知道的是,如果这是查看实际实体的正确方法,如果有任何明显的陷阱,我没有看到。

UserController具有以下字段:

 @EJB private UserFacade userFacade; private User user; 

userFacade是一个无状态会话bean,用于持久化并查找User实例。 JSF页面使用user字段来获取和设置用户的属性。

我使用以下方法执行绑定,并伴有两个辅助方法:

 @PostConstruct private void init() { try { user = userFacade.find(getUserPrincipal().getName()); } catch (NullPointerException ex) { // Intentionally left empty -- User is not logged in. } } private HttpServletRequest getHttpServletRequest() { return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); } private Principal getUserPrincipal() { return getHttpServletRequest().getUserPrincipal(); } 

JSF页面使用以下方法来确定要显示的组件(如果用户已经过身份validation,则无需显示登录表单),如果单击“登录”按钮,则对用户进行身份validation,或者注册为单击“注册”按钮时的新用户。

 public boolean isAuthenticated() { return getUserPrincipal() != null; } public void authenticate() { try { getHttpServletRequest().login(user.getEmailAddress(), user.getPassword()); } catch (Exception ex) { // TODO: Handle failed login attempt } } public void register() { userFacade.create(user); } 

这是正确的方法吗?

谢谢!

编辑:

感谢您的投入! 我考虑了一下,虽然我认为将密码移动到另一个表对我来说有点太多了,我认为我可以通过在@RequestScoped分离UserController来解决一些问题AuthenticationController和一个精简的@SessionScoped UserController

AuthenticationController将具有emailAddresspassword字段,由网页的emailAddress和密码字段绑定。 它还将包含public void authenticate()以对用户进行身份validation,然后丢弃凭据。 然后, @SessionScoped UserController可以绑定到相应的User实体,而无需知道密码。 事实上,我相信我可以从User那里删除密码字段。

你提出的方法有一些粗糙的边缘,但在大多数情况下它是非常好的。

如果您打算存储对User实体的引用,那么最好在SessionScoped托管bean中执行此操作。 这有利有弊。 明显的优势是

  • 在所有页面中, User实体在整个应用程序流程中可用。 这意味着您只需要将一个Principal绑定到User实体一次。 如果需要,您可以在所有页面中重复使用绑定值。

不那么明显的缺点是

  • password字段将在存储器中存储相当长的时间。 在最好的情况下,您应该尝试在身份validation尝试后(无论是否成功,无论该字段是否包含clear或hash中的密码)使实体的密码字段无效。 此外,将password字段定义为延迟获取( LAZY FetchType )与默认的eager fetch( EAGER FetchType )相比,这将是非常有意义的。 如果你实现了这个(特别是密码字段的无效),你需要注意涉及在User实体上进行的合并操作的问题; 在这种情况下,最好有一个单独的实体来存储用户的密码(非常不幸,但这就是你必须在多大程度上弯腰以保护密码及其在某些应用程序中的哈希值)。

话虽如此,还有必要确保以下内容:

  • 应谨慎处理匿名用户主体。 如果您没有编写一个强制执行访问控制机制来保护应用程序“私有”页面的filter,您应该更加关注页面中授权逻辑的构建方式,而不是您不必担心所有当你使用filter。 匿名主体就像任何其他主体一样,除了它没有域中的标识支持。 如果Principal to User实体绑定方案由于某种原因失败,则必须使会话无效并再次将用户重定向到登录页面,尤其是如果您的页面依赖于User实体而不是Principal对象来强制执行访问控制检查。
  • 确保您有一个单独的应用程序登录页面。 在接受登录页面中存在的表单中的用户凭据的大多数应用程序中,这是首选; 如果表单在对话框或其他装置中,则通常不需要单独的登录页面。 这是理想的,原因很简单,您希望登录过程实现POST-REDIRECT-GET模式 – 成功登录应用程序的用户必须重定向到应用程序的主页面。 如果不这样做,将导致浏览器刷新(由任何有权访问终端的人执行)重新提交凭证的情况; 很明显,使用modal dialog或类似对象的应用程序不易受此问题的影响。

更新

这是基于编辑过的问题。 如果按照建议实现authenticate方法,则只有在成功进行身份validation后才能实现将User实体绑定到Principal方案。

以下是我的应用程序中类似实现的再现:

 public String authenticate() { String result = null; ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); HttpServletRequest request = (HttpServletRequest) externalContext.getRequest(); try { request.login(userId, password); result = "/private/MainPage.xhtml?faces-redirect=true"; } catch (ServletException ex) { logger.error("Failed to authenticate user.", ex); FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, Messages.getString("Login.InvalidIdOrPasswordMessage"), null); FacesContext.getCurrentInstance().addMessage(null, facesMessage); } return result; } 

从facelet调用的:

  

请注意,对于身份validation成功的情况,请使用POST-REDIRECT-GET模式。 我在重定向之前留下了一些与当前会话失效有关的代码,以防止会话固定攻击。 User实体与Principal的绑定将在新会话中完成,只要它在会话范围的bean中完成即可。