• 利用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;
    	}
    
  • 相关阅读:
    房地产英语 Real estate词汇
    自制Flash FLV视频播放器
    .net反编译工具Reflector下载
    JQUery插件thickbox
    50 Great Photoshop Tutorials for Clever Beginners
    AspNet中你或许不知道的技巧(转)
    常用的设计网站(收藏)
    35 Green and Earthy Photoshop Effects
    使用 ASP.NET 2.0 增强网站的安全性
    asp.net中实现登陆的时候用SSL
  • 原文地址:https://www.cnblogs.com/coderhuang/p/5994055.html
Copyright © 2020-2023  润新知