這兩天在學習權限控制模塊。之前看過傳智播客黎活明老師的巴巴運動網視頻教程,裏面就講到權限控制的解決方案,當時也只是看看視頻,沒有動手實踐,雖然說看過幾遍,但是對於系統中的權限控制仍是很迷茫,因此藉着此次機會動手實踐一下。java
黎活明老師的巴巴運動網使用的框架是struts + spring + jpa,大體思路是使用自定義註解,在須要權限控制的方法前使用註解定義方法所需的權限,而後使用AOP攔截訪問的方法,在執行目標對象前經過反射取得目標對象所需的權限,而後從當前session中取得登錄用戶,遍歷用戶所擁有的權限,若是有權限則繼續執行目標對象,若是沒有權限則跳轉到錯誤提示頁面。巴巴運動網使用的struts + spring + jpa應用這種方案是有問題的,大體是spring aop沒法攔截經過反射調用的方法,而後黎活明老師經過定製RequestProcessor 解決了這個問題。具體權限實現方案及對這種方案應用到巴巴運動網的缺陷的分析能夠參看黎活明老師的巴巴運動網視頻教程。spring
我在實踐的過程當中採用的是spring mvc + spring + hibernate 的框架,所以使用spring aop攔截是徹底能夠實現這種權限方案的。所以我在系統中定義了一個切面,申明瞭切入點及一個環繞通知。session
如下是被攔截的方法申明,方法上有作權限控制的註解Permissionmvc
- @Permission(module="user",operation="select")
- @RequestMapping(value="/detail/{uid}",method=RequestMethod.GET)
- public String detail(@PathVariable int uid,Model model){
- ....
- }
如下是AOP定義app
- @Pointcut("execution(java.lang.String com.jiangnan.cms.controller..*.*(..))")
- public void controller(){}
- @Around("controller()")
- public Object introcepter(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("攔截到了" + pjp.getSignature().getName() +"方法...");
- }
測試的時候日誌中的確輸出了被攔截的方法。但是pjp.getSignature().getName()只是方法的名稱,作權限控制須要獲得方法上的註解Permission,那麼就須要獲取目標對象上的Method對象,經過Method對象的getAnnotation(Permission.class)方法獲取註解。但是怎麼怎麼直接獲取Method對象而不是方法名稱呢?經過pjp.getSignature()方法獲取的Signature方法上好像沒有直接getMethod()方法,因而去問度娘,得出以下的轉換:框架
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
這下終於知足需求了,因而迅速的寫出了權限控制的代碼:學習
- public Object introcepter(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("攔截到了" + pjp.getSignature().getName() +"方法...");
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
-
- Class clazz = targetMethod.getClass();
- if(clazz.isAnnotationPresent(Permission.class)){
-
- Permission permission = (Permission)clazz.getAnnotation(Permission.class);
- String module = permission.module();
- String operation = permission.operation();
- Privilege privilege = new Privilege(new PrivilegePK(module, operation));
-
- User user = (User)ContextUtils.getHttpSession().getAttribute("employer");
- if(null != user){
- System.out.println(user.getUsername());
- }
- Set<Role> roles = user.getRoles();
- for(Role role : roles){
- if(role.getPrivileges().contains(privilege)){
-
- return pjp.proceed();
- }
- }
-
- throw new PermissionException();
- }
- return pjp.proceed();
- }
而後接着測試,但是在測試的過程當中發現對於須要權限驗證的detail方法,竟然直接執行了,權限控制並無起做用,因而debug調試,發現測試
- clazz.isAnnotationPresent(Permission.class)
返回的是false,檢查下,被攔截的方法上有Permission註解啊,並且註解的定義是方法級別的,做用範圍是運行期啊,這個沒錯啊,重啓了Eclipse,從新發布了,結果仍是這樣,鬱悶啊。。。接着把生成的.class文件反編譯,看到類上有Permission註解的呀,真是百思不得其解啊。。。實在想不明白,關燈睡覺了。ui
次日,內心總是想着這個問題,牽掛着他,很難受啊!忽然靈光一閃,會不會這個獲取到得Method對象不是目標對象上的Method對象,由於經過檢查,目標類上的Method上是的確有那個註解的,除非攔截到的Method對象不是目標對象上的,是代理對象上的,而這個代理對象上的這個方法上沒有Permission註解。而後去網上搜了下這方面的資料,原來spring aop使用cglib生成的代理是不會加上父類的方法上的註解的,也就是這邊生成的代理類上的方法上沒有Permission註解,而後也看到了一篇老外的文章,上面有所提到,但那時針對接口實現的代理,大意是經過接口生成的代理,經過spa
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
這段代碼獲取的targetMethod對象是接口上的方法,他上面也是沒有註解的(原文地址http://stackoverflow.com/questions/5714411/getting-the-java-lang-reflect-method-from-a-proceedingjoinpoint)。可是我這邊不是經過接口生成代理的啊,是使用cglib經過繼承目標對象生成代理的啊,難道這邊獲取的targetMethod對象是代理對象上的?因而就想證實。因而在代碼中輸出如下信息:
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
- System.out.println("classname:" + targetMethod.getDeclaringClass().getName());
- System.out.println("superclass:" + targetMethod.getDeclaringClass().getSuperclass().getName());
- System.out.println("isinterface:" + targetMethod.getDeclaringClass().isInterface());
- System.out.println("target:" + pjp.getTarget().getClass().getName());
- System.out.println("proxy:" + pjp.getThis().getClass().getName());
- System.out.println("method:" + targetMethod.getName());
結果以下:
- classname:com.jiangnan.cms.controller.LogonController
- superclass:java.lang.Object
- isinterface:false
- target:com.jiangnan.cms.controller.LogonController
- proxy:com.jiangnan.cms.controller.LogonController
EnhancerByCGLIB
f6998fd8
- method:logon
其餘的均可以理解,按理3來講classname輸出的應該是代理對象,應該是com.jiangnan.cms.controller.LogonController
EnhancerByCGLIB
f6998fd8,而不是com.jiangnan.cms.controller.LogonController,由於經過methodSignature對象獲取的Method上沒有Permission註解,因此經過getDeclaringClass獲取定義該method的對象應該是代理對象而不是目標對象啊,這個無法解釋啊,實驗結果不能使人滿意啊,不知道如何進行,但願有大神知道,或者之後想明白了再來完善。。。
至此,經過MethodSignature也沒法直接獲取目標對象的被攔截Method對象,那就只能用最笨的辦法了,經過反射獲取,剛纔提到的一篇文章(http://stackoverflow.com/questions/5714411/getting-the-java-lang-reflect-method-from-a-proceedingjoinpoint)有說明,代碼以下:
- Class[] parameterTypes = new Class[pjp.getArgs().length];
- Object[] args = pjp.getArgs();
- for(int i=0; i<args.length; i++) {
- if(args[i] != null) {
- parameterTypes[i] = args[i].getClass();
- }else {
- parameterTypes[i] = null;
- }
- }
- String methodName = pjp.getSignature().getName();
- Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
這種方式能實現,可是麻煩了點,簡化了下,最終代碼以下:
- Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
此處獲取到的realMethod就是目標對象上的,realMethod.isAnnotationPresent(Permission.class)返回的是true。 對於這些東西仍是要親身實踐的,視頻要看,尤爲是好的視頻,要看不止一遍,可是看過了必定要動手實踐,看看視頻是簡單的,徹底不知道本身動手會遇到這些問題,不過遇到問題是好事,解決這些問題本身才能成長!