在JSF中登录,注销和存储会话属性的最佳方式

Mor*_*lor 2 session jsf login myfaces

我是JSF的新手,我对管理用户登录的最佳实践感到困惑.有许多答案浮出水面,但它们需要JSF和Java EE的知识,而我似乎并不知道.

使用经典JSP项目时,我以非常简单的方式处理登录.当用户成功登录时,会创建一个值为true的会话属性"isLoggedIn".当用户注销时,会话无效.如果"isLoggedIn"属性为false或缺失,则要求您登录的页面将重定向到登录屏幕.

如何在JSF中完成这类工作?我应该在HttpSession或托管会话bean中存储登录状态,用户名和用户角色等内容吗?

我发现很多答案都说你应该使用request.getRemoteUser()来管理登录,但是不做任何进一步的解释.根据我能够收集的内容,如果您使用域凭据登录应用程序,此方法很有用.如果我在数据库中存储用户凭据(用户名+ salted和哈希密码),那么它有用吗?

如果有人能告诉我如何使用以下两页的网站,那将非常有用:

  1. 一个登录页面,可以输入用户名和密码,然后按"登录"按钮.
  2. 包含"登录成功"消息的页面和注销按钮.按下注销按钮后,用户将注销并进入登录页面.

如果用户在未登录时尝试转到第二页,则会将其重定向到第一页.

就像我之前说过的,我是JSF的新手,我根本找不到一个如何处理这类事情的好教程.因此,任何您认为有用的链接将不胜感激.

如果重要,我正在使用MyFaces实现和PrimeFaces组件库.

aro*_*oth 6

您有两个基本选项:

  1. 使用基于容器的身份验证.
  2. 滚动你自己.

第一个选项是官方推荐的方法,具体细节将根据您使用的servlet容器和/或Web框架而有所不同.

然而,坦率地说,我经常发现配置基于容器的身份验证比它的价值更麻烦(比简单地构建一个能够满足我想要的自定义身份验证层更麻烦).所以,如果你有兴趣自己滚动,我通常采用的方法(使用Spring和Hibernate)就是拥有一个User类:

@Entity
@Table(name="users")
@NamedQueries({
    @NamedQuery(name="User.findAll", query="SELECT u FROM User u"),
    @NamedQuery(name="User.findByPrimaryEmail", query="SELECT u FROM User u WHERE u.primaryEmail = :email")
})
public class User {
    //fields
    private long id;
    private String primaryEmail;
    private String firstName;
    private String lastName;
    private String hashedPassword;
    private String salt;
    //...

    //relationships
    //...

    public User() {
        primaryEmail = null;
        firstName = null;
        lastName = null;
        salt = null;
        hashedPassword = null;
        //...
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }

    @Column(unique=true)
    public String getPrimaryEmail() {
        return primaryEmail;
    }
    public void setPrimaryEmail(String email) {
        this.primaryEmail = email;
        if (this.primaryEmail != null) {
            this.primaryEmail = email.toLowerCase();
        }
    }

    @Column
    public String getHashedPassword() {
        return hashedPassword;
    }
    public void setHashedPassword(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    @Column
    public String getSalt() {
        return salt;
    }
    public void setSalt(String salt) {
        this.salt = salt;
    }

    //(getters and setters for any other columns and relationships)

    @Transient
    public void setPassword(String passwordPlaintext) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (this.getSalt() == null) {
            this.setSalt(StringUtilities.randomStringOfLength(16));
        }

        this.setHashedPassword(this.computeHash(passwordPlaintext, this.getSalt()));
    }

    @Transient
    public boolean checkPasswordForLogin(String passwordPlaintext) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (StringUtilities.isEmpty(passwordPlaintext)) {
            return false;
        }
        return this.getHashedPassword().equals(this.computeHash(passwordPlaintext, this.getSalt()));
    }

    @Transient
    private String computeHash(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 2048, 160);
        SecretKeyFactory fact = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        //I encode to base64 so that I can treat the hash as text in computations and when storing it in the DB
        return Base64.encodeBytes(fact.generateSecret(spec).getEncoded());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后是一个简单的登录表单,如:

<form id="loginForm" method="POST" action="/r/submitLogin">
    <div class="formRow">
        <span class="formLabel">Email</span>  <input type="text" class="textInput" name="email" />
    </div>
    <div class="formRow">
        <span class="formLabel">Password</span>  <input type="password" class="textInput" name="pass" />
    </div>
    <div class="formRow">
        <input type="submit" class="submitButton" value="Log In" />
    </div>
</form>
Run Code Online (Sandbox Code Playgroud)

并且submitLogin执行类似于:

public ModelAndView submitLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String email = request.getParameter("email");
    String pass = request.getParameter("pass");

    EntityManager em = DatabaseUtil.getEntityManager(request);  //get an EntityManager, you can also use dependency-injection to do this if you prefer
    User user = getUserByPrimaryEmail(email, em);  //lookup the user by email address
    if (user == null) {
        //invalid username
        request.setAttribute("error", "User not found");
        return login(request, response);
    }

    try {
        if (user.checkPasswordForLogin(pass)) {
            //valid login, remember the user in the session
            request.getSession().setAttribute(Constants.SESSION_USER_KEY, user);

            //send the user to the default page
            response.sendRedirect("/r/indexPage");
            return null;
        }
        else {
            //invalid password
            request.setAttribute("error", "Incorrect password");
            return login(request, response);
        }
    }
    catch (Exception e) {
        //should only happen if checkPasswordForLogin() throws NoSuchAlgorithmException/InvalidKeySpecException
        LOG.error("Login processing failed!", e);
        request.setAttribute("error", "Cannot generate password hash?!?!?");
        return login(request, response);
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是它的全部内容,无论如何都是基本的实现.您当然可以在此基础上构建功能,例如"记住我"cookie /持久登录,用户角色,状态,访问级别等.但是对于基本的登录/注销,这是你真正需要的.

使用这种方法,登录用户将可以通过执行request.getSession().getAttribute(Constants.USER_KEY),其中Constants.USER_KEY只是您定义的一些任意字符串.我通常使用"<appName> .user"这样的内容.

如果使用基于容器的身份验证,则通常会通过调用来公开用户request.getUserPrincipal().