zoukankan      html  css  js  c++  java
  • 来玩Play框架06 用户验证

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

     

    用户验证(User Authentification)复合的使用Play框架的数个功能,包括前面已经了解的表单和数据库,以及这篇文章里要提到的加密和会话。根据应用或站点的复杂程度,用户验证也可以随之变化。这里将介绍用户验证的一个基本实现方式。

     

    加密

    为了信息安全,用户密码需要加密,而不是保存为明文。Bcrypt算法可以对明文密码进行哈希(Hash)转换。我保存在数据库中的密码,是经过转换后的文本。

     

    JBcrypt是一个外部的包,提供了Bcrypt功能。要在build.sbt中说明这个包的来源和版本:

    name := "test"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache,
      "mysql" % "mysql-connector-java" % "5.1.18",
      "org.mindrot" % "jbcrypt" % "0.3m"
    )
    
    play.Project.playJavaSettings

    即上面新增的jbcrypt行。重新运行Play后即可使用。为了Eclipse能自动补齐该包的相关调用,可以使用play eclipse,并重新在Eclipse引入项目。

     

    我下面用一个小例子,来说明该Bcrypt的哈希转换。在Play中增加动作:

    public static Result bcrypt() {
        String passwordHash = BCrypt.hashpw("Hello",BCrypt.gensalt());
        boolean correct = BCrypt.checkpw("Hello", passwordHash);
        boolean wrong = BCrypt.checkpw("World", passwordHash);
        return ok(passwordHash + " " + correct + " " + wrong);
    }

    上面程序需引入org.mindrot.jbcrypt.BCrypt。动作中对"Hello"字符串进行了哈希转换,并验证"Hello"和"World"是否为原始的明文文本。

     

    在routes增加对应URL,/bcrypt

    GET     /bcrypt                     controllers.Application.bcrypt()

     

    访问页面:

    用户注册

    有了表单数据库和加密的基础,用户注册很容易实现。首先建立数据模型app/models/User.java:

    package models;
    
    import javax.persistence.*;
    import play.db.ebean.Model;
    import org.mindrot.jbcrypt.BCrypt;
    
    
    
    @Entity
    public class User extends Model {
        @Id    
        private String email;
        private String password;
        
        // Constructor
        public User(String email, String password) {
            String passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
            this.email = email;
            this.password = passwordHash;
        }
    }

    这段代码创建了User类,包含两个属性email和password。在构造器中,我对密码进行了哈希转换。

    下面修改控制器Application(app/controllers/Application.java)。控制器中包含两个动作和一个表单类Registration。一个动作register()用于显示注册页面,另一个动作postRegister处理表单提交的信息,并增加相应的数据库记录。Registration则对应注册页面所显示的表格:

    package controllers;
    
    import play.*;
    import play.mvc.*;
    import play.data.Form;
    import play.data.validation.Constraints.*;import models.User;
    
    public class Application extends Controller {
        public static class Registration {
            @Email
            public String email;
            @Required
            public String password;
        }
        
        public static Result register() {
            Form<Registration> userForm = Form.form(Registration.class);
            return ok(views.html.register.render(userForm));
        }
        
    
        public static Result postRegister() {
            Form<Registration> userForm = 
                    Form.form(Registration.class).bindFromRequest();
            User user = new User(userForm.get().email, userForm.get().password);
            user.save(); 
            return ok("registered"); 
        }
    }

    register()动作使用的模板为app/views/register.scala.html:

    @(userForm: Form[controllers.Application.Registration])
    
    <!DOCTYPE html>
    <html>
      <body>
        <h1> Registration </h1>
        @helper.form(action = routes.Application.postRegister()) {
          @helper.inputText(userForm("email"))
          @helper.inputPassword(userForm("password"))
          <input type="submit">
        }
      </body>
    </html>

    在routes中为两个动作增加对应的URL:

    GET     /register                   controllers.Application.register()
    POST    /register                   controllers.Application.postRegister()

    访问页面:

    输入用户名和密码,可以看到数据库中增加的记录:

    用户验证

    将用户验证的主要逻辑放入到模型User中。修改User类,为User类增加authenticate()方法:

    package models;
    
    import javax.persistence.*;
    import play.db.ebean.Model;
    import org.mindrot.jbcrypt.BCrypt;
    
    @Entity
    public class User extends Model {
        @Id    
        private String email;
        private String password;
        
        // Constructor
        public User(String email, String password) {
            String passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
            this.email = email;
            this.password = passwordHash;
        }
    
        // Query
        public static Model.Finder<Integer, User> find = 
            new Model.Finder<>(Integer.class, User.class);
                
        // Authentification
        public static User authenticate(String email, String password) {
            User user =  find.where()
                    .eq("email", email)
                    .findUnique();
            if (user == null) {
                return user;
            } else if (BCrypt.checkpw(password, user.password)) {
                return user;
            } else {
                return null;
            }
        }
    }

    authenticate()接收的是明文密码。上面的验证中,首先检查用户邮箱是否存在。如果存在,则检查密码是否符合数据库的记录。如果邮箱或者密码错误,将返回null。否则返回正确的用户对象。

    我进一步修改控制器Application。这一次还是增加两个动作和一个表单类。动作login()用于显示登录页面,动作postLogin()用于处理登录表单填写的信息,并根据信息决定是否登入用户。Login类对应登录页面的表单。

    package controllers;
    
    import play.*;
    import play.mvc.*;
    
    import play.data.Form;
    import play.data.validation.Constraints.*;
    
    import models.User;
    
    public class Application extends Controller {
    
        public static class Registration {
            @Email
            public String email;
            @Required
            public String password;
        }
        
        
        public static Result register() {
            Form<Registration> userForm = Form.form(Registration.class);
            return ok(views.html.register.render(userForm));
        }
        
    
        public static Result postRegister() {
            Form<Registration> userForm = 
                    Form.form(Registration.class).bindFromRequest();
            User user = new User(userForm.get().email, userForm.get().password);
            user.save(); 
            return ok("registered"); 
        }
    
        public static class Login {
            @Email
            public String email;
            @Required
            public String password;
            
            public String validate() {
                if (User.authenticate(email, password) == null) {
                    return "Invalid user or password";
                } 
                return null;
            }
        }
        
        public static Result login() {
            Form<Login> userForm = Form.form(Login.class);
            return ok(views.html.login.render(userForm));
        }
        
        public static Result postLogin() {
            Form<Login> userForm = Form.form(Login.class).bindFromRequest();
            if (userForm.hasErrors()) {
                return badRequest("Wrong user/password");
            } else {
                return ok("Valid user");
            }
        }
    }

    上面的表单类Login中,增加了validate()方法,并在其中调用User的验证逻辑。正如postLogin()中所示,表单的hasErrors()方法将自动检查validate()方法的返回值。如果validate()方法返回为null,则说明表单无误。postLogin()的if结构,将根据登录是否合法,来返回不同的结果。

    为新增的动作增加对应的URL:

    GET     /login                      controllers.Application.login()
    POST    /login                      controllers.Application.postLogin()

    访问/login页面,并尝试登录。

    会话

    HTTP协议是无状态的。即使我在/login登录成功,但下一次访问时,服务器又会忘记我是谁。HTTP协议可以用会话(Session)的方式,来记录用户的登录信息。在会话有效期内,服务器可以识别相应客户的访问。Play实现会话相当方便。

    提交登录表格时,如果登录合法,我将让服务器开启和该客户的会话,记录客户的信息。因此,修改postLogin()为:

        public static Result postLogin() {
            Form<Login> userForm = Form.form(Login.class).bindFromRequest();
            if (userForm.hasErrors()) {
                return badRequest(views.html.login.render(userForm));
            } else {
                session().clear();
                session("email",userForm.get().email);
                return redirect("/");
            }
        }

    这里用户登录成功后,将启动一个会话。在会话中,可放入键值对(key-value pair)形式的信息。这里的键名为"email",对应值为登录用户的邮箱地址。登录成功后将重新定向到/。

    增加index()动作,对应/这一URL。在该动作中,我调用session中保存的用户信息:

        public static Result index() {
            String email = session("email");
            if (email != null) {
                return ok(email);
            } else {
                return ok("not login");
            }
        }

    增加routes中对应的URL:

    GET     /                           controllers.Application.index()

    访问/login,并登录。成功登录后重新定向到/,页面为:

    可以看到,会话中的信息可以持续到以后的页面访问。为了销毁会话,可以在某个动作中调用:

    session().clear();

    总结

    用户验证

    会话

    欢迎继续阅读“Java快速教程”系列文章

  • 相关阅读:
    PL/SQL中关于时间的操作
    PL/SQL中关于时间的操作
    Master Data Service调用API创建Model
    ASP.NET Postback回调后参数无效
    Silverlight读取Web.config配置文件
    WCF的用户名+密码认证方式
    Trac 经验谈之(5)插件篇
    Cython 0.15,用 OpenMP 并行多核加速 Python!
    BizTalk Accelerator for HL7医疗行业消息路由处理机制
    Silverlight信息加密 通过Rfc2898DeriveBytes类使用基于HMACSHA1的伪随机数生成器实现PBKDF2
  • 原文地址:https://www.cnblogs.com/vamei/p/3721333.html
Copyright © 2011-2022 走看看