Thursday, June 7, 2012

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


本文是接續前三篇文章()而來的,主要在介紹如果使用Auth/Roles進行元件授權(authorization)的動作。

-- Authorization --



在Apache Wicket的Auth/Roles專案中,在身份認證(authentication)完成後必須要針對各項元件進行授權(authorization)的動作,以避免不具權限的使用者進入了受限的頁面。



基本上Auth/Roles專案是使用RoleAuthorizationStrategy(註1)讓Application得以使用Role來進行權限的設定。

權限設定的方式有二個,一個是使用Wicket Metadata;另一個是Annotation。

若要使用metadata的方式,必須使用org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy中的靜態方法authorize(.....),使用時的程式碼如下:

//方法一
//authorize(Class<T> componentClass, String roles)
//設定某角色能否初始化某元件
MetaDataRoleAuthorizationStrategy.authorize(RestrictedPage.class,"Role Name");
//方法二
//authorize(Component component, Action action, String roles)
//設定某角色能否針對某元件進行某項動作
//Action.ENABLE Action.RENDER
MetaDataRoleAuthorizationStrategy.authorize("html embed wicket id",Action.X,"Role Name");


-- 體驗範例程式 --



本文中要介紹的範例程式,將會使用上述二種方法來進行授權的管理(本範例程式不提供登入畫面,只使用固定的點選方式進行使用者的切換,因此Applicaion及Session均不使用先前範例中的Authentication方式)。

首先是使用者的部份,類別名稱為User.java,它是用來進行Model Mapping的,一般而言大多數的Model均需實作Serializable介面,但是這裡實作用的是Apache Wicket中自訂的Serializable稱為ICluserable(主要用途在於in-memory的處理)。
在此類別中,有二個member,一個是uid(使用者名稱)另一個是roles(org.apache.wicket.authroles.authorization.strategies.role.Roles,註2)。

//User.java
package com.myapp.wicket;

import org.apache.wicket.IClusterable;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;

public class User implements IClusterable {

    //使用者名稱
    private final String uid;
    //角色名稱
    private final Roles roles;

    public User(String uid, String roles) {
        if (uid == null) {
            throw new IllegalArgumentException("uid must be not null");
        }
        if (roles == null) {
            throw new IllegalArgumentException("roles must be not null");
        }
        this.uid = uid;
        this.roles = new Roles(roles);
    }

    //呼叫在Roles類別中的hasRole(..)methos
    //若有該角色則回傳true
    public boolean hasRole(String role) {
        return roles.hasRole(role);
    }

    //呼叫在Roles類別中的hasAnyRole(..)methos
    //若本使用者的角色符合任一個roles(參數)裡的角色就回傳true
    public boolean hasAnyRole(Roles roles) {
        return this.roles.hasAnyRole(roles);
    }

    public String getUid() {
        return uid;
    }

    @Override
    public String toString() {
        return uid + " " + roles;
    }
}

接下來看看WebSession的部份,和前幾個範例不同的是,不使用WebSession進行身份驗證的動作(使用固定連結的方式切換使用者),再將角色檢查(RoleChecking)的功能交給繼承IRoleCheckingStrategy的子類別UserRolesAuthorizer。


//mysession.java
package com.myapp.wicket;

import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.Request;

public class mysession extends WebSession {
    //目前的使用者

    private User user = Application.USERS.get(0);

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

    public User getUser() {
        return user;
    }

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

在Application的部份,同樣的不繼承AuthenticatedWebApplication類別,只在init()中註冊授權策略,並使用角色授權策略。同時使用了meta data的方式設定AdminPage的授權。

//Application.java
package com.myapp.wicket;

import java.util.Arrays;
import java.util.List;
import org.apache.wicket.Session;
import org.apache.wicket.authroles.authorization.strategies.role.RoleAuthorizationStrategy;
import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;

public class Application extends WebApplication {

    //固定只有3個使用者,最後一個使用者並沒有傳入角色名稱
    public static List<User> USERS = Arrays.asList(new User("jon", "ADMIN"),
            new User("kay", "USER"), new User("pam", ""));

    public Application() {
        super();
    }

    @Override
    public Class getHomePage() {
        return HomePage.class;
    }

    @Override
    public Session newSession(Request request, Response response) {
        return new mysession(request);
    }

    @Override
    protected void init() {
        super.init();
        
        this.getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
        //註冊授權策略
        //使用角色授權策略
        getSecuritySettings().setAuthorizationStrategy(
                new RoleAuthorizationStrategy(new UserRolesAuthorizer()));
        //使用wicket metadata(html markup)的方式進行授權管理
        MetaDataRoleAuthorizationStrategy.authorize(AdminPage.class, "ADMIN");
        //使用annotation的方式進行授,就不需要以下這行
        //MetaDataRoleAuthorizationStrategy.authorize(AdminAnnotationPage.class, "ADMIN");
    }
}


當使用者登入成功(通過身份認證)後,必須要有一個類別來進行角色的檢查,UserRolesAuthorizer類別就是用來做角色檢查的。它必須繼承IRoleCheckingStrategy介面,並實作角色檢查的method(public boolean hasAnyRole(Roles roles) {...})。

//UserRolesAuthorizer.java
package com.myapp.wicket;

import org.apache.wicket.Session;
import org.apache.wicket.authroles.authorization.strategies.role.IRoleCheckingStrategy;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;

public class UserRolesAuthorizer implements IRoleCheckingStrategy {

    public UserRolesAuthorizer() {
    }

    //實作此method
    //此部份可以更加複雜,例如只允許某使用者在9AM-4PM具有ADMIN角色
    //當使用者通過身份認證後,在此進行角色檢查
    @Override
    public boolean hasAnyRole(Roles roles) {
        //取得WebSession中已通過認證的User
        //在此先用Roles中的hasAnyRole進行簡易檢查
        mysession authSession = (mysession) Session.get();
        return authSession.getUser().hasAnyRole(roles);
    }
}


在本範例程式中,只有在首頁呈現兩個需要管理員身份才能進入的bookmarkable超連結,其中AdminPage.html使用meta data方式授權,而AdminAnnotationPage.html使用annotation方式進行授權。


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd"  
      xml:lang="en"  
      lang="en"> 
    <head> 
        <wicket:head> 
            <title>Apache Wicket Auth/Roles -- Authorization</title> 
        </wicket:head> 
    </head> 
    <body> 
        <wicket:extend> 
            <div>
                目前的使用者是:
                <strong>
                    <span wicket:id="currentUser">
                        [user]
                    </span>
                </strong>
                <br />
                變更使用者:
                <ul>
                    <li wicket:id="users">
                        <a href="#" wicket:id="selectUserLink">
                            <span wicket:id="userId">
                                [user id]
                            </span>
                        </a>
                    </li>
                </ul>
            </div>

            <br />
            
            <div>
   <a href="#" wicket:id="adminBookmarkableLink">
    前往bookmarkable AdminPage.html<br/>(使用metadata方式授權)
   </a>
  </div>
  <br />
  <div>
   <a href="#" wicket:id="adminAnnotBookmarkableLink">
    前往bookmarkable AdminAnnotationPage.html<br/>(使用annotation方式授權)
   </a>
  </div>
                <br />
        </wicket:extend> 
    </body> 
</html>

和HomePage.html相對應的HomePage.java程式。

//HomePage.java
package com.myapp.wicket;

import org.apache.wicket.Session;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;

public class HomePage extends BasePage {

    public HomePage() {
        add(new Label("currentUser", new PropertyModel<User>(this, "session.user")));
        add(new ListView<User>("users", Application.USERS) {

            @Override
            protected void populateItem(ListItem<User> item) {
                final User user = item.getModelObject();
                item.add(new Link("selectUserLink") {

                    @Override
                    public void onClick() {
                        mysession session = (mysession) Session.get();
                        session.setUser(user);
                    }
                }.add(new Label("userId", new Model<User>(user))));
            }
        });
        //連到使用meta data授權方式的AdminPagelhtml
        add(new BookmarkablePageLink<Void>("adminBookmarkableLink", AdminPage.class));
        //連到使用annotation授權方式的AdminAnnotationPagelhtml
        add(new BookmarkablePageLink<Void>("adminAnnotBookmarkableLink",
                AdminAnnotationPage.class));
    }
}

只有ADMIN才能進入的AdminPage.html及其相對應的AdminPage.java(使用meta data方式授權,所在在AdminPage.java中沒有任何和授權相關的程式碼,它的設定已在Application#init()中指定)。


        <wicket:extend>
            這個頁面只有管理人員看得到,此頁面授權使用meta data方式處理
        </wicket:extend>

//AdminPage.java
package com.myapp.wicket;

public final class AdminPage extends BasePage {
    public AdminPage() {
        super ();
    }
}

只有ADMIN才能進入的AdminAnnotationPage.html及其相對應的AdminAnnotationPage.java(使用annotation方式授權)。


        <wicket:extend>
            本頁面只有管理員才看得到,使用annotation方式進行授權
        </wicket:extend>


#AdminAnnotationPage.java
package com.myapp.wicket;
import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;

//本頁面使用annotation方式進行授權
//本語法指定只有ADMIN才能初始化本頁面
@AuthorizeInstantiation("ADMIN")
public final class AdminAnnotationPage extends BasePage {
    public AdminAnnotationPage() {
        super ();
    }

}

本文先在此打住,下一篇文章將會指定頁面中各別元件的授權。

註1:
RoleAuthorizationStrategy是Auth/Roles專案中,實作IAuthorizationStrategy的類別。RoleAuthorizationStrategy的建構子必須傳入一個實作IRoleCheckingStrategy的子類別。

註2:
org.apache.wicket.authroles.authorization.strategies.role.Roles裡的角色名稱是以逗號(common)來分隔的,例如"ADMIN,USER"。

No comments:

Post a Comment