绑定用户实体和GlassFish Principal

Lau*_*ens 7 java jsf glassfish java-ee

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

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

UserController 具有以下字段:

@EJB
private UserFacade userFacade;

private User user;
Run Code Online (Sandbox Code Playgroud)

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

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

@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();
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

这是正确的方法吗?

谢谢!

编辑:

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

AuthenticationController会拥有emailAddresspassword领域,通过网页的EMAILADDRESS和密码字段的约束.它还将包含public void authenticate()用于验证用户并随后丢弃凭证的用户.然后,@SessionScoped UserController可以绑定到适当的User实体,而无需知道密码.事实上,我相信我可以User完全删除密码字段.

Vin*_*lds 3

您提出的方法有一些粗糙的地方,但在大多数情况下它是相当好的。

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

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

不太明显的缺点是

  • password字段将在内存中存储相当长的一段时间。最好的情况是,您应该在尝试身份验证后尝试使实体的密码字段无效(无论是否成功,无论该字段包含明文密码还是散列密码)。password此外,将字段定义为延迟获取 ( FetchTypeof LAZY) 而不是默认的急切获取 ( FetchTypeof )也很有意义EAGER。如果您实现这一点(特别是密码字段的无效),您将需要注意涉及对实体进行合并操作的问题User;在这种情况下,最好有一个单独的实体来存储用户的密码(非常不幸,但这就是您必须在某些应用程序中保护密码及其哈希值的程度)。

话虽如此,还需要确保以下几点:

  • 应谨慎处理匿名用户主体。如果您没有编写一个强制访问控制机制来保护应用程序的“私有”页面的过滤器,那么您应该更多地担心页面中授权逻辑的构建方式,而不是您不必担心当您使用过滤器时。匿名主体与任何其他主体一样,只是它不受领域中身份的支持。如果主体到用户实体绑定方案由于某种原因失败,您必须使会话无效并再次将用户重定向到登录页面,特别是如果您的页面依赖实体User而不是Principal对象来强制执行访问控制检查。
  • 确保您有一个单独的应用程序登录页面。这对于大多数接受登录页面中存在的表单形式的用户凭据的应用程序来说是更可取的;如果表单位于对话框或其他一些装置中,则通常不需要单独的登录页面。这是可取的,原因很简单,您希望登录过程实现 POST-REDIRECT-GET 模式 - 成功登录到您的应用程序的用户必须重定向到应用程序的主页。如果不这样做,将导致浏览器刷新(由有权访问终端的任何人执行)将重新提交凭据;很明显,使用模式对话框或类似内容的应用程序不太容易受到此问题的影响。

更新

这是基于编辑的问题。如果您按照建议的方法实现authenticate,则可以实现将实体绑定UserPrincipal唯一成功身份验证的方案。

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

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;
}
Run Code Online (Sandbox Code Playgroud)

这是从 Facelet 调用的:

<h:form id="LoginForm" acceptcharset="UTF-8">
    <p>
        <h:outputLabel for="userid" value="#{msg['Login.userid.label']}" />
        <h:inputText id="userid" value="#{loginBean.userId}" />
    </p>
    <p>
        <h:outputLabel for="password" value="#{msg['Login.password.label']}" />
        <h:inputSecret id="password" value="#{loginBean.password}" />
    </p>
        <h:commandButton id="submit" value="#{msg['Login.submit.label']}"
            action="#{loginBean.authenticate}" />
</h:form>
Run Code Online (Sandbox Code Playgroud)

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