基于 Annotation 拦截的 Spring AOP 权限验证方法

标签: annotation spring aop | 发表时间:2014-12-29 19:37 | 作者:coffeehot
出处:http://www.iteye.com

转自: http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilter/index.html

 

使用 Annotation 可以非常方便的根据用户的不同角色,分配访问 Java 方法的权限。在 Java Web 开发中,使用这种方法,可以提高系统的松耦合度,方便维护。

 

在 Web 开发过程中,一个非常理想的开发过程是,开发人员在开发中并不需要关心权限问题,不需要在 Java 方法中写很多逻辑判断去判断用户是否具有合适的角色和权限,这样开发会花费非常多的人力成本,因为所有的开发人员都需要了解关于权限的详细内容,也非常不 容易进行后期维护。我们希望有专门的很少数量的开发人员了解权限内容,并且可以随时方便的修改和配置。于是,我们使用 Annotation,在 Java 方法之前使用 Annotation 可以非常方便的添加,修改和删除对于权限的管理功能。

本文描述了在开发过程中经常遇到的关于权限验证问题的一个典型应用案例,这个案例描述如下:系统要求只有登录用户才可以下定单。通过这个简单的例子,我们将看到如何完成整个系统的权限控制。

本文的开发环境如下:

  • Struts2
  • Spring 3.0
  • JDK1.6
  • AspectJ 6.9

本文将分为以下几个章节,详细描述提出的权限验证方法:

  1. AOP 的基本概念
  2. 权限验证系统架构详细讲解

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 包有

需要的 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,就可以进行登录判断。可见,通过这种方式,只需要很少的人力就可以管理整个系统的权限控制。可以很好的控制项目开发的成本,增加系 统的灵活性。

 

参考资料

学习

讨论

  • 加入 developerWorks 中文社区:查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。


已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [annotation spring aop] 推荐:

基于 Annotation 拦截的 Spring AOP 权限验证方法

- - 企业架构 - ITeye博客
转自: http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilter/index.html. 使用 Annotation 可以非常方便的根据用户的不同角色,分配访问 Java 方法的权限. 在 Java Web 开发中,使用这种方法,可以提高系统的松耦合度,方便维护.

Spring AOP详解

- - Java - 编程语言 - ITeye博客
        最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP来解决. 一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容. 例如,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 1.对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况.

Spring AOP监控SQL执行

- - CSDN博客架构设计推荐文章
         对数据库连接池Proxool比较熟悉的读者,都知道Proxool可以记录SQL执行内容和时间等信息日志. 我们可以将该日志记录专门的SQL日志文件,对于查找执行特别耗时的SQL起了不小的作用. 对于一些其他连接池,没有该特性时,本文介绍Spring AOP切面方法来记录SQL日志.

Spring AOP 实现原理与 CGLIB 应用

- - 博客 - 伯乐在线
来源: IBM Developerworks. 简介: AOP(Aspect Orient Programming),也就是面向方面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等.

Spring AOP 代理机制 JDK&CGLIB

- - 开源软件 - ITeye博客
Spring AOP使用JDK动态代理或者CGLIB来为目标对象创建代理. (建议优先使用JDK的动态代理). 如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理. 所有该目标类型实现的接口都将被代理. 若该目标对象没有实现任何接口,则创建一个CGLIB代理. 如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法) 那也可以.

使用spring AOP获得session的思路

- - RSS - IT博客云
由于Spring 的AOP面向切面编程,与Servlet容器没有任何关联,所以想要获得Session会话比较麻烦. 当然Struts2同样不依赖Servlet容器,可以在Spring AOP中可以使用 com.opensymphony.xwork2.ActionContext,就可以获得 Session.

Spring AOP + Redis缓存数据库查询

- - 编程语言 - ITeye博客
我们希望能够将数据库查询结果缓存到Redis中,这样在第二次做同样的查询时便可以直接从redis取结果,从而减少数据库读写次数. 必须要做到与业务逻辑代码完全分离. 从缓存中读出的数据必须与数据库中的数据一致. 如何为一个数据库查询结果生成一个唯一的标识. Key),能唯一确定一个查询结果,同一个查询结果,一定能映射到同一个.

Spring AOP动态代理原理与实现方式 (转)

- - 开源软件 - ITeye博客
AOP:面向切面、面向方面、面向接口是一种横切技术. 1.事务管理: (1)数据库事务:(2)编程事务(3)声明事物:Spring AOP-->声明事物   . 3.安全验证: Spring AOP---OOP升级  . 静态代理原理:目标对象:调用业务逻辑    代理对象:日志管理. 表示层调用--->代理对象(日志管理)-->调用目标对象.

Spring aop 原理及各种应用场景

- - 开源软件 - ITeye博客
AOP是Aspect Oriented Programing的简称,面向切面编程. AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理、缓存、对象池管理以及日志记录. AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中. AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强.

Spring aop--几种不同的使用方式

- - CSDN博客推荐文章
        最近听老师讲关于AOP关于容器的课,突然觉得之前对aop的理解都不是很到位,先不说理解就是应用都很少,最近也写了几篇关于AOP的博客,都是一些简单的demo,今天在这里再说一下关于Spring AOP的几种实现方式.         我们经常会用到的有如下几种.         1、基于代理的AOP.