1. 程式人生 > >使用Spring Aop實現許可權攔截功能

使用Spring Aop實現許可權攔截功能

    最近在做的一個系統需要實現許可權攔截功能,主要是防止一些惡意的使用者直接輸入URL來對我們的系統造成破壞。

下面來說以下具體的實現:

首先看一下我們定義的Aspect類

package com.hhoj.judger.aspect;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import com.hhoj.judger.annotation.ValidatePermission;
import com.hhoj.judger.entity.User;
import com.hhoj.judger.util.HttpObjectHolder;

@Aspect
public class PermissionAspect {

    @Pointcut(value="@annotation(com.hhoj.judger.annotation.ValidatePermission)")
    public void validate() {}
    
    @Around(value="validate()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Method method=getSourceMethod(pjp);
        if(method!=null) {
            ValidatePermission vp=method.getAnnotation(ValidatePermission.class);
            int role=vp.role();
            HttpServletRequest request=HttpObjectHolder.getCurrentRequest();
            HttpServletResponse response=HttpObjectHolder.getCurrentResponse();
            User user=(User)request.getSession().getAttribute("currentUser");
            if(user==null||user.getRole()<role) {
                /**
                 * 許可權不足直接跳轉到提醒頁面
                 */
                String path=request.getContextPath();
                response.sendRedirect(path+"/authenticationFailure");
                return null;
            }
        }
        return pjp.proceed();
    }
    
    /**
     * 獲取被攔截的方法
     * @param jp
     * @return
     */
     private Method getSourceMethod(JoinPoint jp){
            Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
            try {
                return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            }
            return null;
        }
}

該Aspect主要是對被 ValidatePermission 註解的方法實行攔截,然後通過反射獲取註解中的引數值,再獲取當前使用者的角色,通過判斷當前使用者是否有許可權執行該方法,如果許可權不夠則跳轉到提示錯誤頁面,否則繼續執行。該過程中唯一不好解決的問題就是Request,Response等內建物件的獲取。

    我們可以採取兩種方案:

1.  在Controller控制類中,每個需要被攔截的方法上都新增 HttpServletRequest,HttpServletResponse引數,Spring Mvc在處理請求的時候會自動為我們注入引數值,然後我們在Aspect中通過 ProceedingJoinPoint的getArgs 獲取被攔截方法的引數,這樣就可以取到我們需要的request和reponse,這個方法的弊端就是我們需要在每個被攔截的方法上都新增上HttpServletRequest,HttpServletResponse引數,在需要攔截的方法數目較多的情況下這也是個不小的工作量,這還不是主要的問題,我們大部分Controller中的處理方法都不需要用到HttpServletRequest或者HttpServletResponse,在一個方法上新增它永不到的引數,這種實現方法實在不太雅觀。

2. 通過一個ThreadLocal對我們的request,或response進行繫結。需要用到的時候就在ThreadLocal中獲取,通過該方法我們不僅僅可以在Aspect中使用這些內建物件,只要是我們想用,在任何地方都能使用。那麼還有一個問題,這些物件應該在什麼地方繫結?答案就是我們可以在攔截器中進行繫結,不管是Servlet的攔截器,還是Spring Mvc的攔截器都可以,這裡我採用的是Spring Mvc的攔截器。下面是我的實現過程:

package com.hhoj.judger.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.hhoj.judger.util.HttpObjectHolder;
/**
 * 將Request,和Response繫結到當前執行緒
 * 使用者AOP攔截時使用
 * @author zhu
 *
 */
public class SaveHttpObjectFilter implements HandlerInterceptor{

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HttpObjectHolder.setCurrentRequest((HttpServletRequest)request);
		HttpObjectHolder.setCurrentResponse((HttpServletResponse)response);
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}

}

package com.hhoj.judger.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用於儲存每個執行緒的request 和Response
 * @author zhu
 *
 */
public final class HttpObjectHolder {
	private static ThreadLocal<HttpServletRequest>requestThreadLocal=new ThreadLocal<>();
	private static ThreadLocal<HttpServletResponse>responseThreadLocal=new ThreadLocal<>();
	public static HttpServletRequest getCurrentRequest() {
		return requestThreadLocal.get();
	}
	public static void setCurrentRequest(HttpServletRequest request) {
		requestThreadLocal.set(request);
	}
	public static HttpServletResponse getCurrentResponse() {
		return responseThreadLocal.get();
	}
	public static void setCurrentResponse(HttpServletResponse response) {
		responseThreadLocal.set(response);
	}
}

在來看看 ValidatePermission註解的定義

package com.hhoj.judger.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 被此註解標註的方法需要進行許可權校驗
 * @author zhu
 *
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidatePermission {
	int role () ;
}

然後在我們的Spring配置檔案中進行配置,由於我們採用的是註解方式的Aop 所以只要在配置檔案中加入

 <aop:aspectj-autoproxy />

這裡有個特別需要注意的地方!!由於我採用的是Spring +Spring MVC的開發模式,所以會產生兩個Bean容器,Spring會建立一個,Spring MVC也會建立一個。Spring MVC的Bean 容器會把Spring容器當作是它的父容器,    這一點從Spring MVC的原始碼中就可以看出來,這是簡化後的Spring MVC的Bean容器初始化過程。

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);                                                                                    
					}
 					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		return wac;
	}

       rootContext 就是Spring的Bean容器,而cawc就是Spring MVC的Bean 容器。在我們查詢Bean的時候子容器是可以查到到父容器中的Bean的,但是反過來就不行了。一般我們配置的時候都喜歡把Service等其他的Bean配置到Spring配置檔案中,而把Controller配置到Spring MVC的配置檔案中,那麼這就就會產生一個問題,Spring MVC中的Controller Bean對Spring中的Bean 是不可見的,也就是說我們把AOP配置在Spring配置檔案中,在執行的時候它是找不到我們的Contrller的。所以就會產生AOP配置不起作用的現象。解決方案:將Controller的Bean配置與Spring AOP的配置放在同一個配置檔案中。