最近在項目(IDEA+Spring+JDK1.6+)中,為了控制使用者的讀寫權限,使用了AOP技術,将權限控制定義成了一個切面(Aspect)。
1、AOP的基本概念
AOP是spring架構中的重要特性,英文為Aspect Oriented Programming ,意思是面向切面程式設計。
我們在系統開發中可以提取出很多共性的東西作為一個Aspect,可以了解為在系統中,我們需要很多次重複實作的功能。
比如日志列印,判斷使用者是否已登入,判斷頁面的讀寫權限等等。
1.1 AOP 重要的概念
Join Point:表示在程式中明确定義的執行點,典型的 Join Point 包括方法調用,對類成員的通路以及異常處理程式塊的執行等等,它自身還可以嵌套其它 Join Point。
PointCut:表示一組 Join Point,這些 Join Point 或是通過邏輯關系組合起來,或是通過通配、正規表達式等方式集中起來,它定義了相應的 Advice 将要發生的地方。
Advice:Advice 定義了在 PointCut 裡面定義的程式點具體要做的操作,它通過 before、after 和 around 來差別是在每個 Join Point 之前、之後還是代替執行的代碼。
1.2 通知(Advice)類型
前置通知(Before advice):在某連接配接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接配接點前的執行。
後置通知(After advice):當某連接配接點退出的時候執行的通知(不論是正常傳回還是異常退出)。
傳回後通知(After return advice):在某連接配接點正常完成後執行的通知,不包括抛出異常的情況。
環繞通知(Around advice) :包圍一個連接配接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行為,也可以選擇不執行。
抛出異常後通知(After throwing advice) : 在方法抛出異常退出時執行的通知。
本項目中使用了環繞通知,因為我們需要判斷權限之後選擇切點函數是否執行。如果權限滿足,那麼執行切點函數,如果不滿足直接傳回權限不夠的消息。
2、基于自定義Annotation 的Spring AOP 權限驗證方法的實作
2.1 配置AOP
在項目中首先按照通常的做法在applicationContext.xml 中配置了代理
在webmvc.xml中配置了<aop:aspectj-autoproxy/>後成功支援AOP。
使用的IDEA程式設計環境,aop的依賴配置都會被自動載入。
2.2 自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Auth {
Level level() default Level.ANON;
public static enum Level {
ANON(0),
LOGIN(1),
READ(2),
EDIT(3),
ADMIN(4);
private int value;
Level(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
2.3實作切面
再利用spring的AOP特性關聯一個切面到Auth切點Level。
@Pointcut("@annotation(Auth) || within(@Auth *)")
public void requireAuth() {}
定義環繞通知。
@Around("requireAuth()")
public Object auth(ProceedingJoinPoint point) throws Throwable {
Auth auth = getAuth(point);
if (auth != null) {
// 目前level
Integer level = auth.level().getValue();
System.out.println(level);
// TODO 擷取使用者資訊、目前appkey資訊
// 擷取使用者資訊和appkey
User user = UserUtils.getUser();
String appkey = (String)ThreadContext.get("appkey");
Boolean hasAuth = authDenied.hasAuth(appkey, level, user.getLogin());
if (hasAuth) {
System.out.println("-----beforeAdvice().invoke-----" + point.getSignature());
Object response = point.proceed(point.getArgs());
System.out.println("-----afterAdvice().invoke-----" + point.getSignature());
System.out.println(response);
return response;
} else {
// throw exception or goto require auth page
return "redirect:/perDenied?appkey=" + appkey;
}
} else {
return point.proceed(point.getArgs());
}
}
2.4注解切點
最後在需要權限控制的controller函數上使用注解。
@Auth(level = Auth.Level.EDIT)
3、總結
通過這種AOP的環繞通知的方式,可以非常友善地實作系統的權限控制,開發成本低,并且注解的使用非常靈活,很友善區分是否要進行權限控制。