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

标签: annotation spring aop | 发表时间:2014-12-29 11: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_声明式事务

- - CSDN博客架构设计推荐文章
改进一下,因为这两次操作属于同一个业务,将转账的两个事务放到一个事务中,就会避免这样的问题,因为如果A减了1000后,如果发生异常,之后会回滚,此时两个要么同时成功,要么同时失败,这正好满足了事务的原子性和一致性,这种方式重新定义了事务的边界,所以讲事务管理加到BIZ层是合理的设计. 那么模仿上面的例子实现一个Spring的事务管理:.

Spring AOP 代理机制 JDK&CGLIB

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

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获得session的思路

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

Spring AOP + Redis缓存数据库查询

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

spring mvc +spring aop结合注解的 用户操作日志记录

- - 行业应用 - ITeye博客
参考了网上的一些 文章 但是他们写的不是很全  自己也是经过了一些摸索  可以实现 记录 spring mvc controller层操作记录. 一个关注点的模块化,这个关注点可能会横切多个对象. 事务管理是J2EE应用中一个关于横切关注点的很好的例子. AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现.

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

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