利用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;
	}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM