Ch7 : Play Seesion & Flash

[Session & Flash]
SessionWeb中很常見儲存資料的方式之ㄧ,大多時候會儲存在使用者的瀏覽器中,伺服器端會有驗證使用者的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,是帳號登入的類別,裡面有accountpassword屬性,方便我們後續帳號的資訊取得。

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.htmlok.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

results matching ""

    No results matching ""