基於Spring Aop實現類似shiro的簡單權限校驗功能


在我們的web開發過程中,經常需要用到功能權限校驗,驗證用戶是否有某個角色或者權限,目前有很多框架,如Shiro

Shiro有基於自定義登錄界面的版本,也有基於CAS登錄的版本,目前我們的系統是基於CAS單點登錄,各個公司的單點登錄機制略有差異,和Shiro CAS的標准單點登錄校驗方式也自然略有不同。

在嘗試將自定義登錄的普通版Shiro改造失敗后,在系統登錄、校驗角色、權限我認為相對簡單后,覺得模仿Shiro自己實現一個權限校驗小框架,說是框架,其實就是一個aop advisor,幾個注解(Shiro不就是這個功能嗎)

 

RequiresRoles和RequiresPermissions相似,不截圖了

接下來是實現aop攔截功能了,先來說下注解的用法,和Shiro的注解一樣,都是放在class或者method上

上述配置的意思是需要角色user和admin,需要權限home:*,上面僅是一個例子,實際業務中的角色已經確定有哪些權限了。

 

 以下是aop攔截方式

 

/**
 * 權限校驗advisor,負責對RequiresRoles、RequiresPermissions標記的controller進行權限校驗
 * 他執行的時機是interceptor之后、方法執行之前
 * Created by Administrator on 2015/2/9.
 */
@Aspect
@Component
public class AuthExecuteAdvisor implements PointcutAdvisor, Pointcut {

    private static final Logger logger = LoggerFactory.getLogger(AuthExecuteAdvisor.class);

    @Autowired
    private UserService userService;

    /**
     * 生成一個始終匹配class的Filter對象,任何類都可以通過這個過濾器
     */
    private static final ClassFilter TrueClassFilter = new ClassFilter() {
        @Override
        public boolean matches(Class<?> clazz) {
            return true;
        }
    };

    /**
     * 生成根據特定注解進行過濾的方法匹配器
     * 如果class上有注解、或方法上有注解、或接口方法上有注解,都能通過
     */
    private MethodMatcher annotationMethodMatcher = new StaticMethodMatcher() {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {

            if (targetClass.isAnnotationPresent(RequiresRoles.class) || targetClass.isAnnotationPresent(RequiresPermissions.class)) {
                return true;
            }

            if (method.isAnnotationPresent(RequiresRoles.class) || method.isAnnotationPresent(RequiresPermissions.class)) {
                return true;
            }
            // The method may be on an interface, so let's check on the target class as well.
            Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);


            return (specificMethod != method && (specificMethod.isAnnotationPresent(RequiresRoles.class)
                    || specificMethod.isAnnotationPresent(RequiresPermissions.class)));
        }

    };

    @Override
    public ClassFilter getClassFilter() {
        //只執行注解的類
        return TrueClassFilter;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return annotationMethodMatcher;
    }

    /**
     * 當前對象就是切面對象,根據classFilter、MethodFilter定義執行范圍
     *
     * @return
     */
    @Override
    public Pointcut getPointcut() {
        return this;
    }

    /**
     * 切面對應的處理策略
     *
     * @return
     */
    @Override
    public Advice getAdvice() {
        //這個MethodInterceptor相當於MethodBeforeAdvice
        return new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {

                LoginContext loginContext = ActionContext.getLoginContext();
                if (loginContext == null) {
                    throw new AuthFailException("校驗用戶權限失敗,用戶未登錄");
                }

                // TODO: 2017/11/21 管理員全權限
                if (loginContext.getUserType().equals(0)) {
                    return invocation.proceed();
                }

                Set<String> alreadyRoles = new HashSet<>(userService.getRolesByUserId(loginContext.getUserId()));
                Set<String> alreadyPermissions = new HashSet<>(userService.getPermissionByUserId(loginContext.getUserId()));

                List<String> requireRole = getRequiredRole(invocation);
                List<String> requirePermission = getRequiredPermission(invocation);

                if (!CollectionUtils.isEmpty(requireRole) && !alreadyRoles.containsAll(requireRole)) {
                    //role權限不足
                    logger.error("校驗用戶權限失敗,role角色不足");

                    throw new AuthFailException("校驗用戶權限失敗,role角色不足");
                }

                if (!CollectionUtils.isEmpty(requirePermission) && !alreadyPermissions.containsAll(requirePermission)) {
                    //permission不足
                    logger.error("校驗用戶權限失敗,permission權限不足");
                    throw new AuthFailException("校驗用戶權限失敗,權限不足");
                }

                return invocation.proceed();
            }


        };
    }

    /**
     * 獲取方法需要的角色
     *
     * @param invocation
     * @return
     */
    private List<String> getRequiredRole(MethodInvocation invocation) {
        List<String> requiredRoles = new ArrayList<>();
        Class<?> clazz = invocation.getThis().getClass();

        if (clazz.isAnnotationPresent(RequiresRoles.class)) {
            Collections.addAll(requiredRoles, clazz.getAnnotation(RequiresRoles.class).value());
        }

        if (invocation.getMethod().isAnnotationPresent(RequiresRoles.class)) {
            Collections.addAll(requiredRoles, invocation.getMethod().getAnnotation(RequiresRoles.class).value());
        }

        return requiredRoles;
    }

    /**
     * 獲取方法需要的權限
     *
     * @param invocation
     * @return
     */
    private List<String> getRequiredPermission(MethodInvocation invocation) {
        List<String> requirePermission = new ArrayList<>();
        Class<?> clazz = invocation.getThis().getClass();

        if (clazz.isAnnotationPresent(RequiresPermissions.class)) {
            Collections.addAll(requirePermission, clazz.getAnnotation(RequiresPermissions.class).value());
        }

        if (invocation.getMethod().isAnnotationPresent(RequiresPermissions.class)) {
            Collections.addAll(requirePermission, invocation.getMethod().getAnnotation(RequiresPermissions.class).value());
        }

        return requirePermission;
    }


    /**
     * 每個切面的通知一個實例,或可分享的實例
     * 此處使用分享的實例即可,分享的實例在內存中只會創建一個Advice對象
     *
     * @return
     */
    @Override
    public boolean isPerInstance() {
        return false;
    }
}

  

此處主要有以下幾個關鍵點:

    

當前類既是一個切入點Pointcut(一群方法),也是一個通知持有者Advisor

Pointcut接口通過ClassFilter、MethodMatcher鎖定哪些方法會用到Advisor

Advisor接口通過getAdvice獲取Pointcut需要進行的攔截操作

 

ClassFilter是一個始終返回True的類過濾器,因為我們攔截執行的最小單位是method,所以即使class上沒有注解,也要讓他通過攔截
MethodMatcher則會判斷method所在的class是否有注解,如果沒有,則判斷method是否有注解,二者滿足其一就能通過校驗

Advice的具體攔截過程為:
獲取登錄上下文
獲取當前method需要的權限和角色(這里實際可以再優化,因為method可能只需要Role或Permission其中一種權限)
判斷當前用戶是否具有這些權限

  

 

開啟spring的aop功能,權限攔截開始生效

 

 

 

 

 


免責聲明!

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



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