Ch7 : Play Seesion & Flash
[Session & Flash]
Session是Web中很常見儲存資料的方式之ㄧ,大多時候會儲存在使用者的瀏覽器中,伺服器端會有驗證使用者的Session是否正確的檢查。優點是就算跨頁面之後,在同ㄧ個的網站下,我們所儲存的資訊,依然可以在使用者的瀏覽器看到相關的Session資訊。它很常用來當作帳號登入後,儲存必要資訊到Session裡。
Flash機制,是Play特有的Session,它主要有兩個特點是 1.Flash只會存活在ㄧ次的Request中,若是離開後,就無法取得資料了。2.Flash所儲存的資料是沒有加密過的訊息,使用者可以藉由瀏覽器的工具來去修改資料。基於這兩個特點,比較合適的使用方式是,用來提示使用者的用途,會是最恰當的,也不會有安全性問題。還有一個是不建議使用ajax方式取得Flash的訊息,只適合用來單純的知道結果的頁面。
以下來簡單嘗試看看,怎樣寫Session&Flash吧!!
專案參考 : https://github.com/loveu8/playMyBtaisMariaDB/tree/Ch7
You don't compare with others. But you must have courage to change your life. Only you can change you.
老樣子,我們先在app/controller新增ㄧ隻SessionController來開始這次練習。這次練習因為方便介紹,沒有特別對帳號與密碼作加密動作,實際網站運行,這些都是需要經過加密才會儲存到使用者的瀏覽器的Session裡的,要特別去注意這點,以免造成使用者資訊暴露在不安全的狀態下。
package controllers;
import play.mvc.Controller;
import play.mvc.Result;
public class SessionController extends Controller{
}
[Session]
我們會在Controller裡寫簡單的帳號登入,登入成功我們會在Seesion寫入登入資訊,最後導入成功的歡迎頁面。若原本帶有Session到了登入頁時,該Session驗證成功的話,會直接導到成功頁面,就不需要再登入ㄧ次了。我們會使用第五章的相關登入程式,來重新改寫。
app/pojo/login
前面章節介紹的User.java,是帳號登入的類別,裡面有account與password屬性,方便我們後續帳號的資訊取得。
package pojo.login;
public class User {
private String account;
private String password;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 簡單驗證帳號密碼
public String validate(String dbAccount , String dbPassword) {
if (nullOrEmptyCheck(account, password)) {
return "請輸入帳號與密碼";
}
if (!accountCheck(dbAccount ,dbPassword , account , password)){
return "請確認帳號與密碼是否輸入正確";
}
// 通過檢查,回傳pass
return "pass";
}
private boolean nullOrEmptyCheck(String account2, String password2) {
return "".equals(account2) || "".equals(password2);
}
private boolean accountCheck(String dbAccount , String dbPassword , String userAccount , String userPassword){
return dbAccount != null && dbPassword != null && !"".equals(dbAccount) && !"".equals(dbPassword)
&& dbAccount.equals(userAccount) && dbPassword.equals(userPassword);
}
}
app/controller/SessionController
package controllers;
import javax.inject.Inject;
import play.data.FormFactory;
import play.mvc.Controller;
import play.mvc.Result;
import pojo.login.User;
import views.html.session.*;
public class SessionController extends Controller{
@Inject
// 相依性注入Play的formFactory
FormFactory formFactory;
// 登入頁面
public Result login(String errorMessage){
// 發現Session有登入資訊,進入驗證登入
if(!"".equals(session().get("account")) && !"".equals(session().get("password"))
&& session().get("account")!=null && session().get("password")!=null){
//發現Session有資料,進入Session驗證登入
play.Logger.info("goSessionAuth");
return goSessionAuth();
} else {
if(!"".equals(errorMessage)){
// 若登出時,需要的提示訊息,印在頁面上
return ok(login.render(errorMessage));
} else{
return ok(login.render(""));
}
}
}
// 登出頁面,登出時清除Play Session
public Result logout(){
session().clear();
play.Logger.info("logout...bye!!");
return redirect(controllers.routes.SessionController.login("登出成功,請重新登入。").url());
}
// 登入驗證
public Result auth(){
return goFromAuth();
}
// Session驗證登入
private Result goSessionAuth(){
User user = new User();
user.setAccount(session().get("account"));
user.setPassword(session().get("password"));
// 使用內建play logger 印出資料
play.Logger.info("account = " + user.getAccount() + " , password = " + user.getPassword());
// 傳入預設帳號與密碼,驗證Session內的帳號與密碼
if("pass".equals(user.validate("tom","123"))){
return ok(ok.render());
} else {
String errorMessage = user.validate("tom","123");
play.Logger.info("login errorMessage = " + errorMessage); // log 印出 錯誤訊息
return ok(login.render(errorMessage)); // 回傳檢查後錯誤的資訊
}
}
// 表單驗證登入
private Result goFromAuth(){
User user = formFactory.form(User.class).bindFromRequest().get(); // 使用Play內建formFactory來對應到User物件
String account = user.getAccount(); // 找出對應物件後,就可以取得表單所儲存的值
String password = user.getPassword();
play.Logger.info("account = " + account + " , password = " + password); // 使用內建play logger 印出表單資料
// 預設帳號密碼是tom , 密碼123 , 測試帳號密碼是否正確
if("pass".equals(user.validate("tom","123"))){
session().put("account", account);
session().put("password", password);
return ok(ok.render());
} else {
// 導回到登入頁面
String errorMessage = user.validate("tom","123");
play.Logger.info("login errorMessage = " + errorMessage); // log 印出 錯誤訊息
return ok(login.render(errorMessage)); // 回傳檢查後錯誤的資訊
}
}
}
app/views/session
新增login.scala.html與ok.scala.html。
login.scala.html
@** errorMessage 用來提示帳號登入錯誤資訊,若是沒有錯誤資訊不會顯示出來 **@
@(errorMessage : String)
<form action='@controllers.routes.SessionController.auth.url' method="post">
帳號 <input type="text" autocomplete="off" name="account" ></input><br>
密碼 <input type="password" autocomplete="off" name="password"></input><br>
@** Play可以在xxx.scala.html,寫scala語言程式,前面需要多增加"@"符號才可以使用**@
@if(!"".equals(errorMessage)){
<span style="color:red;">@errorMessage</span><br>
}
<input type="submit" value="登入">
</form>
ok.scala.html
<form action='@controllers.routes.SessionController.logout.url' method="get">
登入成功!!
<input type="submit" value="登出">
</form>
conf/routes
# http://127.0.0.1:9000/session/login
GET /session/login controllers.SessionController.login(errorMessage : String ?="")
# http://127.0.0.1:9000/session/logout
GET /session/logout controllers.SessionController.logout()
# http://127.0.0.1:9000/session/login
POST /session/login controllers.SessionController.auth()
Request
GET : http://127.0.0.1:9000/session/login
正常登入沒有Session狀態下,輸入帳號:tom,密碼:123,會登入成功。若是已經登入成功,再次使用登入網址的話,我們會檢查Session的帳號資訊,確認無誤後,會直接進入登入成功頁面,而不會再需要再登入ㄧ次了。
Response
我們可以檢查Cookie可以發現有一個Play Session把我們剛剛的帳號資訊,儲存下來了。
Request
接下來,我們按下登出按鈕,我們會在*Controller*清除掉*Play Session*相關登入資訊後,再重新導到登入頁面,以及提示使用者已經登出成功了。
Response
以上範例介紹到一般網站,最常使用的帳號登入的範例,更安全的帳號登入,可以增加驗證碼與自動登入勾選的選項按鈕,以及更完善的Session管理,來檢查加密過的Session帳號資訊,可以自行增加這三個功能,當作練習。
[Flash]
Run,Barry,Run!!。阿不好意思,跑錯棚了(註:DC閃電俠)。Flash機制很合適來當作檢查表單驗證用途,訊息儲存方式跟Java Map相同,是用Key-value方式儲存需要提示的訊息。我們做一個檢查帳號申請輸入的資料,是否同時符合4個以上的英文,再透過Flash提示使用者。
app/controller/SessionController.java
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
...
// flash測試頁面
public Result flashPage(){
// 清除暫存資訊
flash().clear();
return ok(flashTest.render(""));
}
// 檢查後,flash提示訊息
public Result flashCheck(){
DynamicForm requestData = formFactory.form().bindFromRequest(); // 動態取得頁面上的表單
Map <String , String> formData = requestData.get().getData(); // 把form轉換成Map物件
String account = formData.get("account"); // 根據表單欄位的name名稱,取出表單裡面的值
String patternStr = "[a-zA-Z]{4,10}$"; // 只接受英文4的字串
Pattern pattern = Pattern.compile(patternStr);
Matcher matcher = pattern.matcher(account);
boolean isMatcher = matcher.find(); // 是否符合規則
// 清除上次訊息
flash().clear();
if(isMatcher){
flash().put("success", "帳號可以使用");
play.Logger.info("success = " + flash().get("success"));
} else {
flash().put("error", "帳號格式錯誤");
play.Logger.info("error = " + flash().get("error"));
}
return ok(flashTest.render(account));
}
app/views/session
新增flashTest.scala.html頁面。
@(account : String )
@* 檢查後接收 account ,使用者可以不用重新再輸入ㄧ次 *@
<form action='@controllers.routes.SessionController.flashCheck.url' method="post">
申請的帳號 : <input type="text" autocomplete="off" name="account" @if(!"".equals(account)){ value = "@account" }></input>
@if(flash.containsKey("error")) {
<span style="color:red;">@flash.get("error")</span>
}
@if(flash.containsKey("success")) {
<span style="color:green;">@flash.get("success")</span>
}
<br>
*只接收4~10個字數的英文字*<br>
<input type="submit" value="檢查">
</form>
conf/routes
# http://127.0.0.1:9000/session/flashPage
GET /session/flashTest controllers.SessionController.flashPage()
# http://127.0.0.1:9000/session/flashCheck
POST /session/flashTest controllers.SessionController.flashCheck()
Request
GET : http://127.0.0.1:9000/session/flashTest
Response
取得頁面。
帳號不符合格式。
帳號符合格式。
以上可以看到Flash機制很適合用來提示使用者那裡輸入錯誤。您可以看看ㄧ些常見的網站,去看看他們申請帳號時,會需要檢核的欄位資訊,嘗試看看自己實作出來,也建議Flash的錯訊訊息,使用一個Class來管理錯誤訊息,方便以後的文字修改,而不會散落各地,無法整理。
[Final]
這個章節雖然內容不多,而實際製作網站時,會是有用的幫助,我們也可以用Flash機制來去寫帳號訊息提示部份,藉由這些第五章~第七章的範例,可以想想如果自己製作可申請帳號,且可以登入的網頁,應該要怎樣去撰寫。下個章節會介紹RestFul Api的撰寫與呼叫,一步步往前吧!!加油!!
[Reference]
1.JavaSessionFlash
https://www.playframework.com/documentation/2.5.x/JavaSessionFlash
2.Web 技術中的 Session 是什麼?
http://fred-zone.blogspot.tw/2014/01/web-session.html