在 Web 開發過程中,一個非常理想的開發過程是,開發人員在開發中並不需要關心權限問題,不需要在 Java 方法中寫 很多邏輯判斷去判斷用戶是否具有合適的角色和權限,這樣開發會花費非常多的人力成本,因為所有的開發人員都需要了解 關於權限的詳細內容,也非常不容易進行後期維護。我們希望有專門的很少數量的開發人員了解權限內容,並且可以隨時方 便的修改和配置。於是,我們使用 Annotation,在 Java 方法之前使用 Annotation 可以非常方便的添加,修改和刪除對 於權限的管理功能。
本文描述了在開發過程中經常遇到的關於權限驗證問題的一個典型應用案例,這個案例描述如 下:系統要求只有登錄用戶才可以下定單。通過這個簡單的例子,我們將看到如何完成整個系統的權限控制。
本文 的開發環境如下:
Struts2
Spring 3.0
JDK1.6
AspectJ 6.9
本文將分為以下幾個章節,詳細描述提出的權限驗證方法:
AOP 的基本概念
權限驗證系統架構詳細講解
AOP 的基本概念
AOP 是 Aspect Oriented Programming 的縮寫,意思是面向方面的編程。我們在系統開發中可以提取出很多共性的東西作為一個 Aspect, 可以理解為在系統中,我們需要很多次重復實現的功能。比如計算某個方法運行了多少毫秒,判斷用戶是不是具有訪問權限 ,用戶是否已登錄,數據的事務處理,日志記錄等等。
一般我們描述一個故事,都會說什麼時間什麼地點發生了什 麼事情,那 Join Point 的意思是,發生的地點,Advice 就是發生了什麼事,Aspect 就是這個故事的整體,包含了 Join Point 和 Advice。PointCut 又把地點進行了規律性的總結,比如使用正則表達式 (com.example.service.*,即所有在 service 包下面的方法),把所有 Advice 發生的地點進行描述。
讀者現在應該已經大概了解了 AOP 的基本概念, 下面我們再來詳細介紹一下:
Join Point:表示在程序中明確定義的執行點,典型的 Join Point 包括方法調用, 對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它 Join Point。
PointCut:表示一組 Join Point,這些 Join Point 或是通過邏輯關系組合起來,或是通過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
Advice:Advice 定義了在 PointCut 裡面定義的程序點具體要做的操作,它通過 before 、after 和 around 來區別是在每個 Join Point 之前、之後還是代替執行的代碼。
基於 Annotation 的 Spring AOP 權限驗證方法的實現
Spring AOP 目前只支持基於 method 的 Join Points,而不支持基於 fileds 的 Join Points,也可以使用 AspectJ 去實現基於 fields 的 AOP,這並不會破壞 Spring 的核心 API。 Spring AOP 更傾向 於配合 Spring IoC 去解決在企業級系統中更為普遍的問題。
在這個具體的例子中,我們實現了這樣一個場景,在 用戶下訂單的時候,先判斷用戶是否已經登錄,如果用戶沒有登錄,系統將轉到登錄頁面,要求用戶登錄。
1. 配置 applicationContext
在 Spring 中支持 AOP 的配置非常的簡單,只需要在 Spring 配置文件 applicationContext.xml 中添加:
<aop:aspectj-autoproxy/>
同時在 applicationContext.xml 的 schema 中配置:
清單 1
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
在配置時,我們需要將引用的 jar 包放置在 WEB-INF/lib 目錄下面:
需要的 jar 包有
配置這些之後 Spring AOP 就可以開始工作 了。
2. 定義 Annotation
首先,我們定義一個常量來表示用戶是否登錄:
清單 2
package com.example.myenum; public enum ISLOGIN { YES, LOGOUT, NO }
這裡也可以選擇不使用 enum,UserAccessAnnotation 中的 isLogin() 方法也可以返回整數或 String 類 型,返回類型並沒有限制。常量定義之後,我們再定義 Annotation,在 UserAccessAnnotation 中定義 isLogin(),表示 用戶是否已經登錄:
清單 3
package com.example.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.example.myenum.ISLOGIN; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface UserAccessAnnotation { /** * User has been login or not. * */ ISLOGIN isLogin(); }
定義好之後,這個 Annoatation 將可以被放置在需要驗證用戶是否登錄的方法前面,就像下面這樣:
清單 4
package com.example.aspect; public class OrderAction extends BaseAction{ …… @UserAccessAnnotation(>isLogin=ISLOGIN.YES) public String Order(){ try{ Boolean result = orderService.order(Quote quote); if(result) return SUCCESS; }catch(Exception e) { logger.debug(e); this.addActionError(getText("user_no_permission_error")); } return INPUT; } …… }
在這裡我們使用 UserAccessAnnotation 來表示需要在 Order 方法執行之前判斷用戶是否已經登錄,如果沒有 登錄,在 struts2 中,通過下面定義的 Exception 的捕獲機制,將頁面轉到登錄頁面。
3. 在 applicationContext.xml 中定義 Aspect
清單 5
<bean id="permission" class="com.example.aspect.PermissionAspect" scope="prototype"> <property name="authService" ref="AuthService" /> </bean>
我們要在 Spring 中定義 PermissionAspect。在 Struts+Spring 架構中可以把 Aspect 看作是一 個 Action,只不過 Aspect 是其他 Action 的前提條件或者結束動作。Aspect 定義中的 Service 屬性和 Action 中的 Service 屬性沒有任何區別。這裡我們用 AuthService 類來實現判斷用戶是否已經登錄的邏輯。
4. 定義 PointCut
清單 6
@Aspect public class SystemArchitecture { /** * A Join Point is defined in the action layer where the method needs * a permission check. */ @Pointcut("@annotation(com.example.annotation.UserAccessAnnotation)") public void userAccess() {} }
PointCut 即切入點,就是定義方法執行的點,before、after 或者 around。 一般情況下,我們把 PointCut 全部集中定義在 SystemArchitecture 類中,以方便修改和管理。
當實現 Aspect 時可以很方便的使用我們在 SystemArchitecture 中定義的 PointCut。
5. 實現 Aspect
清單 7
package com.example.aspect; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import com.example.annotation.UserAccessAnnotation; import com.example.base.action.BaseAction; import com.example.myenum.USERTYPE; import com.example.service.AuthService; @Aspect public class PermissionAspect extends BaseAction{ …… AuthService authService = null; @Before(value="com.example.aspect.SystemArchitecture.userAccess()&&"+ "@annotation(userAccessAnnotation)",argNames="userAccessAnnotation") public void checkPermission(UserAccessAnnotation userAccessAnnotation) throws Exception{ IsLogin isLogin = userAccessAnnotation.isLogin (); if(!authService.userLogin(user).equals(isLogin.toString())){ throw new NoPermissionException(getText("user_no_permission_error")); } } …… }
在 checkPermission 方法前,我們首先定義 PointCut:
@Before (value="com.example.aspect.SystemArchitecture.userAccess()&&"+
"@annotation (userAccessAnnotation) ",argNames="userAccessAnnotation").
argNames="userAccessAnnotation" 的意思是 把 Annotation 當做參數傳遞進來,並判斷用戶登錄狀態是否與 Annotation 中的定義一致。如果不一致,就要拋出 NoPermissionException,通知系統該用戶沒有權限。
6. 在 Struts action 配置文件中定義 Global Exception
在 Struts.xml 中配置:
清單 8
<global-results> <result name="loginerror">/WEB-INF/jsp/login.jsp</result> </global-results> <global-exception-mappings> <exception-mapping exception="com.example.exceptions.NoPermissionException" result="loginerror"/> </global-exception-mappings>
經過上面的配置,在 NoPermissionException 拋出之後,Struts2 會 catch 這個 exception,並轉到 login.jsp 頁面。
Annotation 的放置位置時非常靈活的,並不局限於放置在 Struts2 的 Action 之前,若您沒有使用 struts,也可以放置在 Service 類的實現方法之前,讓調用方法捕捉 exception 。Aspect 如何處理用戶沒有登錄的情況也可以根據實際需要去實現,同樣不局限於拋出 exception 這種方式。總之,處理 方法是非常靈活的,根據讀者的需要可以隨機應變。
總結
綜上所述,我們利用在 Struts Action 之前增加 Annotation 的方式非常方便的驗證用戶在系統中的訪問權限。需要驗證登錄與否的方法之前,只要簡單的添加 Annotation ,就可以進行登錄判斷。可見,通過這種方式,只需要很少的人力就可以管理整個系統的權限控制。可以很好的控制項目開 發的成本,增加系統的靈活性。