來玩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快速教程”系列文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM