Spring-security源碼-註解權限原理之MethodSecurityInterceptor(二十一)

MethodSecurityInterceptor是Spring Security對於Spring Aop的切入邏輯

 @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //<1>調用方法前的權限校驗
        InterceptorStatusToken token = super.beforeInvocation(mi);
        Object result;
        try {
            result = mi.proceed();
        }
        finally {
            //允許重置ServletContext
            super.finallyInvocation(token);
        }
        //<5>調用方法後的權限校驗
        return super.afterInvocation(token, result);
    }

<1>

org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation

    protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName()
                    + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                    + getSecureObjectClass());
        }
        //<2>基於object獲取對應的ConfigAttribute 也就是我們的權限配置 初始化處參考
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
        //如果配有配置表示不需要權限校驗
        if (CollectionUtils.isEmpty(attributes)) {
            Assert.isTrue(!this.rejectPublicInvocations,
                    () -> "Secure object invocation " + object
                            + " was denied as public invocations are not allowed via this interceptor. "
                            + "This indicates a configuration error because the "
                            + "rejectPublicInvocations property is set to 'true'");
            if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Authorized public object %s", object));
            }
            publishEvent(new PublicInvocationEvent(object));
            return null; // no further work post-invocation
        }
        //如果未登錄 拋出異常
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"), object, attributes);
        }
        //如果登錄過期觸發自動authenticationManager.authenticate 重新認證
        Authentication authenticated = authenticateIfRequired();
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
        }
        // <3>現在有方法 和我們的權限配置則校驗授權
        attemptAuthorization(object, attributes, authenticated);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));
        }
        if (this.publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
        if (runAs != null) {
            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);

            if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));
            }
            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
        this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");
        // no further work post-invocation
        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

    }

<2>

org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource#getAttributes

@Override
    public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
        //方法和class作爲cacheKey
        DelegatingMethodSecurityMetadataSource.DefaultCacheKey cacheKey = new DelegatingMethodSecurityMetadataSource.DefaultCacheKey(method, targetClass);
        //同步鎖
        synchronized (this.attributeCache) {
            //先判斷cache是否有
            Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
            // 如果有直接返回
            if (cached != null) {
                return cached;
            }
            //遍歷MethodSecurityMetadataSource 解析註解獲得ConfigAttribute
            Collection<ConfigAttribute> attributes = null;
            for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
                attributes = s.getAttributes(method, targetClass);
                if (attributes != null && !attributes.isEmpty()) {
                    break;
                }
            }
            // Put it in the cache.
            if (attributes == null || attributes.isEmpty()) {
                this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
                return NULL_CONFIG_ATTRIBUTE;
            }
            this.attributeCache.put(cacheKey, attributes);
            return attributes;
        }
    }

<3>

org.springframework.security.access.intercept.AbstractSecurityInterceptor#attemptAuthorization

 private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
                                      Authentication authenticated) {
        try {
            //<4>交給accessDecisionManager處理 初始化處參考
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException ex) {//針對授權不過的拋出異常
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object,
                        attributes, this.accessDecisionManager));
            }
            else if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
            }
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
            throw ex;
        }
    }

<4>

 @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
        int deny = 0;
        //遍歷voter 調用vote方法進行處理
        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);
            switch (result) {
                //授權通過直接返回
                case AccessDecisionVoter.ACCESS_GRANTED:
                    return;
                    //針對授權不過 返回ACCESS_DENIED
                case AccessDecisionVoter.ACCESS_DENIED:
                    deny++;
                    break;
                default:
                    break;
            }
        }
        if (deny > 0) {
            throw new AccessDeniedException(
                    this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }
        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }

<5>

    protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
        if (token == null) {
            // public object
            return returnedObject;
        }
        finallyInvocation(token); // continue to clean in this method for passivity
        if (this.afterInvocationManager != null) {
            // Attempt after invocation handling
            try {
                returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
                        token.getSecureObject(), token.getAttributes(), returnedObject);
            }
            catch (AccessDeniedException ex) {
                publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(),
                        token.getSecurityContext().getAuthentication(), ex));
                throw ex;
            }
        }
        return returnedObject;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章