Wednesday, June 6, 2012

Apache Wicket Auth/Roles 簡介 -- Authentication (1)

Apache Wicket Auth/Roles是Apache Wicket的延伸套件,它讓認證(Authentication)及授權(Authorization)變得簡單易用。

本系列文章將以Apacke Wicket Example裡的範例來進行說明,Wicket的版本為1.5x。

首先,Auth/Roles套件實作了IAuthorizationStrategy介面;當授權策略(authorization strategy)安裝在安全性設定中(WebApplication#getSecuritySettings),Wicket將會自動檢查所有元件(component)是否具備可以初始化(instaintion)或是呈現畫面(render)的權限。




-- Authentication --


欲進行身份認證時,本身的web application必須繼承AuthenticatedWebApplication(此類別為抽象類別),同時要override裡面的三個methods:

1.newSession:回傳AuthenticatedWebSession的子類別(自訂的Session物件)。

2.getSignInPageClass:回傳登入頁面類別。

3.getWebSessionClass():回傳AbstractAuthenticatedWebSession的子類別。

為了要讓使用者在驗證過後,我們必須在application類別的init()中註冊授權策略(getSecuritySettings().setAuthorizationStrategy(new IAuthorizationStrategy(){...}),同時要override在IAuthorizationStrategy類別中的methods。

1.isInstantiationAuthorized:取得能否建立某項元件。

2.isActionAuthorized:取得能否執行某項行為。


-- 體驗範例程式 --



以下是SignInApplication.java程式碼:
package com.myapp.wicket;

import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.Session;
import org.apache.wicket.authorization.Action;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.component.IRequestableComponent;

/**
 *
 * @author Hsiao Shengche
 * @version
 */
public class SignInApplication extends AuthenticatedWebApplication {

    public SignInApplication() {
    }

    public Class getHomePage() {
        return Home.class;
    }

    @Override
    protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
        return SignInSession.class;
    }

    @Override
    public Session newSession(Request request, Response response) {
        //回傳自訂的Session類別
        return new SignInSession(request);
    }

    @Override
    protected Class<? extends WebPage> getSignInPageClass() {
        //回傳登入頁面
        return SignIn.class;
    }
    
    @Override
    protected void init(){
        super.init();
        //設定字元集
        getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
        //註冊授權策略
        getSecuritySettings().setAuthorizationStrategy(new IAuthorizationStrategy(){

            @Override
            public <T extends IRequestableComponent> boolean isInstantiationAuthorized(Class<T> type) {
                //回傳能否建立元件(含頁面)
                //檢查Home.html是否要驗證才能進入
                //isAssignableFrom(type)->指的是傳入的元件是否就是我們要的元件
                //意指我們只要檢查Home.html此元件而已(若知道runtime的類別,亦可使用instanceof)
                
                if (Home.class.isAssignableFrom(type)){
                    //檢查是否已登入
                    if (((SignInSession)Session.get()).isSignedIn()){
                        //若已登入,則允許執行
                        return true;
                    }
                    //若沒有登入則要導向登入頁面
                    //throw new RestartResponseAtInterceptPageException(SignIn.class);
                    //介入目前的equest並導向其他頁面
                    throw new RestartResponseAtInterceptPageException(SignIn.class);
                }
                return true;
            }

            @Override
            public boolean isActionAuthorized(Component cmpnt, Action action) {
                //回傳是否允許執行某項元件的某個動作,目前先允許所有動作
                return true;
            }
            
        });
    }
}

接下來,我們要提供自訂的session類別,它必須繼承AuthenticatedWebSession,相同的它也是一個抽象類別。自訂session必須要override以下兩個methods:

1.authenticate:在此進行身份驗證,使用username及password。

2.getRoles:當使用者通過身份驗證後會呼叫此method,因此在此method中必須提供相對應的角色(roles)。


以下是SignInSession.java範例程式碼:


package com.myapp.wicket;

import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.request.Request;

/**
 *
 * @author Hsiao Shengche
 */
public class SignInSession extends AuthenticatedWebSession {

    //簡單的使用者
    private String user;

    public SignInSession(Request request) {
        super(request);
    }

    @Override
    public boolean authenticate(String username, String password) {
        //進行身份驗證,預設帳密均為wicket
        final String WICKET = "wicket";

        if (user == null) {
            // 檢查帳密
            if (WICKET.equalsIgnoreCase(username) && WICKET.equalsIgnoreCase(password)) {
                user = username;
            }
        }
        //如果登入成功,那麼AuthenticationWebSession.isSignIn()就會回傳true
        return user != null;
    }

    public String getUser() {
        return user;
    }

    public void setUser(final String user) {
        this.user = user;
    }

    
    @Override
    public Roles getRoles() {
        //此範例不提供角色
        return null;
    }
}


在這個範例中,首頁是Home.html,但它必須先經過身份驗證才能看到,因此系統會將未認證的使用者導向SignIn.html頁面進行輸入帳號、密碼並驗證的動作。
以下是SignIn.html的html:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://wicket.apache.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>SignIn</title>
        <link rel="stylesheet" type="text/css" href="style.css"/>
    </head>
    <body>
        <wicket:extend>
            <span wicket:id = "feedback"/>
            <p>
                <i>Username and password are both "wicket"</i>
            </p>
            <form wicket:id = "signInForm">
                <table>
                    <tr>
                        <td align = "right">Username:</td>
                        <td>
                            <input wicket:id = "username" type = "text" value = "foo@goo.moo" size = "30"/>
                        </td>
                    </tr>
                    <tr>
                        <td align = "right">Password:</td>
                        <td>
                            <input wicket:id = "password" type = "password" value = "password" size="30"/>
                        </td>
                    </tr>
                    <tr>
                        <td></td>
                        <td>
                            <input type = "submit" value = "登入"/>
                        </td>
                    </tr>
                </table> 
            </form>
        </wicket:extend>
    </body>
</html>


SignIn.java程式碼如下:


package com.myapp.wicket;

import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.value.ValueMap;

/**
 *
 * @author Hsiao Shengche
 */
public final class SignIn extends BasePage {

    public SignIn() {
        super();
        // Create feedback panel and add to page
        add(new FeedbackPanel("feedback"));
        // Add sign-in form to page
        add(new SignInForm("signInForm"));
    }

    public SignIn(PageParameters params) {
        //TODO:  process page parameters
    }

    public final class SignInForm extends Form<Void> {

        private static final String USERNAME = "username";
        private static final String PASSWORD = "password";
        // El-cheapo model for form
        private final ValueMap properties = new ValueMap();

        /**
         * Constructor
         *
         * @param id id of the form component
         */
        public SignInForm(final String id) {
            super(id);

            // Attach textfield components that edit properties map model
            add(new TextField<String>(USERNAME, new PropertyModel<String>(properties, USERNAME)));
            add(new PasswordTextField(PASSWORD, new PropertyModel<String>(properties, PASSWORD)));
        }

        @Override
        public final void onSubmit() {
            //按下送出鍵就進行驗證
            SignInSession session = getMySession();

            // 呼叫AuthenticatedWebSession.signIn(username,password)
            // 會自動呼叫WebSession.authenticate(username,password)
            if (session.signIn(getUsername(), getPassword())) {
                if (!continueToOriginalDestination()) {
                    //導入首頁->表示已驗證通過
                    setResponsePage(getApplication().getHomePage());
                }
            } else {
                // 設定錯誤訊息
                String errmsg = getString("loginError", null, "登入失敗");

                // Register the error message with the feedback panel
                error(errmsg);
            }
        }

        private String getPassword() {
            return properties.getString(PASSWORD);
        }

       
        private String getUsername() {
            return properties.getString(USERNAME);
        }

        private SignInSession getMySession() {
            return (SignInSession) getSession();
        }
    }
}


SignOut.html則是登出頁面,按了它會執行SignOut.java並將Session設為invalide,意即取消此次Session所儲存的資料。


以下是SignOut.html內容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://wicket.apache.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>SignOut</title>
        <link rel="stylesheet" type="text/css" href="style.css"/>
    </head>
    <body>
        <wicket:extend>
            <h2>Goodbye!</h2>    
            <wicket:link><a href="Home.html">首頁</a></wicket:link>
        </wicket:extend>
    </body>
</html>


以下是SignOut.java程式碼:

package com.myapp.wicket;

import org.apache.wicket.request.mapper.parameter.PageParameters;

/**
 *
 * @author Hsiao Shengche
 */
public final class SignOut extends BasePage {

    /*
     * 若要成功登出則要取消此建構子
    public SignOut() {
        super();
    }
    * 
    */
    
    public SignOut(PageParameters params) {
        getSession().invalidate();
    }
}

No comments:

Post a Comment