利用spring AOP 实现统一校验

开发环境

  • JDK: 1.7
  • spring: 4.0.6
  • aspect: 1.7.4

应用背景

  在APP与后台通讯的过程中,我们一般都会有个authToken的字符串校验,判断那些请求是需要校验用户信息的,因为APP用户并不需要登录到我们的后台系统,所以一些基于session的权限控制(比如shiro)并不合适,所以导致我们又回到了解放前,很多请求都需要先校验这个用户的信息,不通过的就重定向到登录界面,比如下面的代码:

    AppUser user = appUserService.getUserByAuthToken(appointmentSearchVo.getAuthToken());
    if (null == user) {
	throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL);
    }

  可以看到在很多的控制器下都会有这样一段代码,太丑陋了,像这些重复的但又必不可少的操作,用AOP的方式做统一处理是很优雅的,我的思路是:

  • 1.定义一个查询父类,里面包含到authToken跟usedId两个属性,所有需要校验用户的请求的查询参数都继承这个查询父类,之所以会有这个userId,是因为我们校验得到用户之后,需要根据用户Id获取一些用户数据的,所以在AOP层我们就回填了这个参数了,这样也不会影响之前的代码逻辑(这个可能跟我的业务需求有关了)
public class AuthSearchVO {
	
	public String authToken; //校验字符串
	
	public Integer userId; //APP用户Id
	
	public final String getAuthToken() {
		return authToken;
	}

	public final void setAuthToken(String authToken) {
		this.authToken = authToken;
	}

	public final Integer getUserId() {
		return userId;
	}

	public final void setUserId(Integer userId) {
		this.userId = userId;
	}

	@Override
	public String toString() {
		return "SearchVO [authToken=" + authToken + ", userId=" + userId + "]";
	}

}
  • 2.定义一个方法级的注解,所有需要校验的请求都加上这个注解,用于AOP的拦截(当然你也可以拦截所有控制器的请求)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
	String type();
}
  • 3.AOP处理,之所以会将注解作为参数传进来,是因为考虑到可能会有多个APP的校验,可以利用注解的type属性加以区分
public class AuthTokenAOPInterceptor {
	
	@Resource
	private AppUserService appUserService;
	
	private static final String authFieldName = "authToken";
	private static final String userIdFieldName = "userId";
	
	public void before(JoinPoint joinPoint, AuthToken authToken) throws Throwable{
		
		Object[] args =  joinPoint.getArgs(); //获取拦截方法的参数
 		boolean isFound = false;
		for(Object arg : args){
			if(arg != null){
				Class<?> clazz = arg.getClass();//利用反射获取属性值
				Field[]  fields =  clazz.getDeclaredFields();
				int authIndex = -1;
				int userIdIndex = -1;
   				for(int i = 0; i < fields.length; i++){
					Field field = fields[i];
					field.setAccessible(true);
					if(authFieldName.equals(field.getName())){//包含校验Token
						authIndex = i;
					}else if(userIdFieldName.equals(field.getName())){//包含用户Id
						userIdIndex = i;
					}
				}
				
				if(authIndex >= 0 & userIdIndex >= 0){
					isFound = true;
					authTokenCheck(fields[authIndex], fields[userIdIndex], arg, authToken);//校验用户
					break;
				}
			}
		}
		if(!isFound){
			throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL);
		}
		
	}
	
	private void  authTokenCheck(Field authField, Field userIdField, Object arg, AuthToken authToken) throws Exception{
		if(String.class == authField.getType()){
			String authTokenStr = (String)authField.get(arg);//获取到校验Token
			AppUser user = appUserService.getUserByAuthToken(authTokenStr);
			if(user != null){
				userIdField.set(arg, user.getId());
			}else{
				throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL);
			}
		}
		
	}
}
  • 4.最后就是在配置文件中配置这个AOP了(因为我们的spring版本跟aspect版本有点出入,导致用不了基于注解的方式)
	<bean id="authTokenAOPInterceptor" class="com.distinct.app.web.common.auth.AuthTokenAOPInterceptor"/>
	<aop:config proxy-target-class="true">
		<aop:pointcut id="authCheckPointcut" expression="@annotation(authToken)"/>
		<aop:aspect ref="authTokenAOPInterceptor" order="1">
            <aop:before method="before" pointcut-ref="authCheckPointcut"/>
        </aop:aspect>
	</aop:config>

  最后给出测试代码,这样的代码就优雅很多了

	@RequestMapping(value = "/appointments", method = { RequestMethod.GET })
	@ResponseBody
	@AuthToken(type="disticntApp")
	public List<AppointmentVo> getAppointments(AppointmentSearchVo appointmentSearchVo) {
		List<AppointmentVo> appointments = appointmentService.getAppointment(appointmentSearchVo.getUserId(), appointmentSearchVo);
		return appointments;
	}
posted @ 2016-10-24 20:41  coderhuang  阅读(6146)  评论(2编辑  收藏  举报