【Spring Security】2. EableWebSecurity、WebSecurityConfiguration和過濾器鏈源碼解析

1 @EnableWebSecurity源碼解析

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
    boolean debug() default false;
}
  1. @EnableWebSecurity是開啓spring security的默認行爲。

  2. @Import(WebSecurityConfiguration.class) 將WebSecurityConfiguration類放入Spring的IoC容器中,WebSecurityConfiguration完成初始化。

  3. debug參數默認爲false,用於指定是否採用調試模式,在調試模式下,每個請求的詳細信息和所經過的過濾器,以及調用棧都會被打印到控制檯。

2 WebSecurityConfiguration源碼解析

  1. setFilterChainProxySecurityConfigurer方法

    該方法中用Spring Security配置文件中繼承的WebSecurityConfigurerAdapter的配置類來初始化Security Configurer列表,Security Configurer列表是啓用所需的安全策略的依據。WebSecurityConfigurer繼承了WebSecurityConfigurerAdapter的配置器。

public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
        this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
        if (this.debugEnabled != null) {
            this.webSecurity.debug(this.debugEnabled);
        }
​
        webSecurityConfigurers.sort(WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE);
        Integer previousOrder = null;
        Object previousConfig = null;
​
        Iterator var5;
        SecurityConfigurer config;
        for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) {
            config = (SecurityConfigurer)var5.next();
            Integer order = WebSecurityConfiguration.AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
            }
​
            previousOrder = order;
        }
​
        var5 = webSecurityConfigurers.iterator();
​
        while(var5.hasNext()) {
            config = (SecurityConfigurer)var5.next();
            // 將配置的每一個SecurityConfigurer列表傳遞給WebSecurity
            this.webSecurity.apply(config);
        }
​
        this.webSecurityConfigurers = webSecurityConfigurers;
    }
  1. springSecurityFilterChain()構造過濾器鏈

    該方法提供了一個名爲springSecurityFilterChain的bean,返回一個Filter對象。

  @Bean(
        name = {"springSecurityFilterChain"}
    )
    public Filter springSecurityFilterChain() throws Exception {
        // 如果沒有配置過webSecurityConfigurers則會用WebSecurityConfigurerAdapter中的配置作爲默認行爲
        boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
            });
            this.webSecurity.apply(adapter);
        }
        // 通過調用WebSecurity的build方法生成過濾器
        return (Filter)this.webSecurity.build();
    }
​

Spring Security的核心是一條過濾器鏈,Spring Security生效的關鍵是Web.xml中配置的Spring Security提供的過濾器。DelegatingFilterProxy是Spring提供的Servlet Filter代理,通過下面的配置注入springSecurityFilterChain的bean,並代理這個過濾器。Springboot中自動配置了Web.xml。

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

WebSecurity的build方法最終調用的是doBuild,由WebSecurity繼承的AbstractConfiguredSecurityBuilder提供

    public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = this.doBuild();
            return this.object;
        } else {
            throw new AlreadyBuiltException("This object has already been built");
        }
    }
    protected abstract O doBuild() throws Exception;

doBuild方法中調用了WebSecurity的performBuild方法

    protected final O doBuild() throws Exception {
        synchronized(this.configurers) {
            // 按照狀態一次執行相應的方法
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
            this.beforeInit();
            // 初始化狀態,將所有的HttpSecurity添加到WebSecurity中
            this.init();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
            this.beforeConfigure();
            this.configure();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
            // 在BUILDING階段調用WebSecurity的performBuild方法
            O result = this.performBuild();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
            return result;
        }
    }

在preformBuild方法中Spring Security完成了所有過濾器的構建,最終返回一個過濾器鏈代理類filterChainProxy。

    protected Filter performBuild() throws Exception {
        Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> {
            return "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly";
        });
        int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
        List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);
        Iterator var3 = this.ignoredRequests.iterator();
​
        while(var3.hasNext()) {
            RequestMatcher ignoredRequest = (RequestMatcher)var3.next();
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0]));
        }
​
        var3 = this.securityFilterChainBuilders.iterator();
​
        while(var3.hasNext()) {
            SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();
            // 每個HttpSecurity生成一條過濾器鏈
            // HttpSecurity來自於繼承了WebSecurityConfigureAdapter的WebSecurityConfigure配置文件
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        // filterChainProxy間接繼承了Filter 可以作爲過濾器使用
        // 攜帶了若干條過濾器鏈,將過濾器職責派發到鏈上的每個過濾器上
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (this.httpFirewall != null) {
            filterChainProxy.setFirewall(this.httpFirewall);
        }
​
        filterChainProxy.afterPropertiesSet();
        Filter result = filterChainProxy;
        if (this.debugEnabled) {
            this.logger.warn("\n\n********************************************************************\n**********        Security debugging is enabled.       *************\n**********    This may include sensitive information.  *************\n**********      Do not use in a production system!     *************\n********************************************************************\n\n");
            result = new DebugFilter(filterChainProxy);
        }
​
        this.postBuildAction.run();
        return (Filter)result;
    }

filterChainProxy攜帶了若干條過濾器鏈,將過濾器職責派發到鏈上的每個過濾器上。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                // 派發到過濾器鏈上 
                this.doFilterInternal(request, response, chain);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            this.doFilterInternal(request, response, chain);
        }

doFilterInternal是真正執行虛擬過濾器鏈邏輯的方法。

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // HTTP防火牆
        FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
       // 根據配置的RequestMatcher決定每個請求經過哪些過濾器
        List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
        if (filters != null && filters.size() != 0) {
            // 將所有的過濾器合併成一條虛擬過濾器鏈
            FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
           // 模擬過濾器的執行流程,執行整條過濾器鏈
            vfc.doFilter(fwRequest, fwResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
            }
​
            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
        }
    }
​

chain.doFilter(fwRequest, fwResponse) 調用的是VirtualFilterChain類中的方法。

    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<? extends Filter> additionalFilters;
        private int currentPosition = 0;
​
        public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
        }
​
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.currentPosition == this.additionalFilters.size()) {
                // 執行過濾器鏈後,調用真實的FilterChain,完成原生過濾器的剩餘邏輯
                this.originalChain.doFilter(request, response);
            } else {
                ++this.currentPosition;
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                // 通過改變下標回調的方式按照順序執行每個過濾器
                nextFilter.doFilter(request, response, this);
            }
​
        }
    }

3 過濾器鏈源碼解析

FilterComparator維護了一個有序的過濾器註冊表filterToOrder

​    FilterComparator() {
        FilterComparator.Step order = new FilterComparator.Step(100, 100);
        this.put(ChannelProcessingFilter.class, order.next());
        this.put(ConcurrentSessionFilter.class, order.next());
        this.put(WebAsyncManagerIntegrationFilter.class, order.next());
        this.put(SecurityContextPersistenceFilter.class, order.next());
        this.put(HeaderWriterFilter.class, order.next());
        this.put(CorsFilter.class, order.next());
        this.put(CsrfFilter.class, order.next());
        this.put(LogoutFilter.class, order.next());
        // ......
    }

一些主要過濾器的順序如下圖所示:

3.1 ChannelProcessingFilter

用於過濾請求使用http協議還是https協議。

private ChannelDecisionManager channelDecisionManager; // 用於判斷當前請求與協議是否相符
private FilterInvocationSecurityMetadataSource securityMetadataSource; // 用於儲存請求與協議的對應關係

doFilter方法做了兩件事,首先找出當前請求需要的協議,attr的值有:

  • ANY_CHANNEL:http、https

  • REQUIRES_SECURE_CHANNEL:https

  • REQUIRES_INSECURE_CHANNEL:http

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        // 找出當前請求所需要的協議
        Collection<ConfigAttribute> attr = this.securityMetadataSource.getAttributes(fi);
        if (attr != null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request: " + fi.toString() + "; ConfigAttributes: " + attr);
            }
            //  判斷當前請求是否與協議相符
            this.channelDecisionManager.decide(fi, attr);
            if (fi.getResponse().isCommitted()) {
                return;
            }
        }
        // 跳到下一個過濾器
        chain.doFilter(request, response);
    }

判斷當前請求是否與協議相符. 若不相符, 則修改協議並自動跳轉。

    public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException {
        Iterator var3 = config.iterator();
​
        while(var3.hasNext()) {
            ConfigAttribute attribute = (ConfigAttribute)var3.next();
            if ("ANY_CHANNEL".equals(attribute.getAttribute())) {
                return;
            }
        }
​
        var3 = this.channelProcessors.iterator();
​
        while(var3.hasNext()) {
            ChannelProcessor processor = (ChannelProcessor)var3.next();
            processor.decide(invocation, config);
            if (invocation.getResponse().isCommitted()) {
                break;
            }
        }
​
    }

3.2 SecurityContextPersistenceFilter

用於控制SecurityContext在一次請求中的生命週期,請求來臨時,創建SecurityContext安全上下文信息,請求結束時清空SecurityContextHolder。

在WebSecurityConfigure配置文件中通過HttpSecurity.securityContext()引入配置對象SecurityContextConfigure進行配置

3.3 UsernamePasswordAuthenticationFilter

用於使用用戶名和密碼進行身份驗證的過濾器,通過HttpSecurity.formLogin進行配置。

   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }
​
            if (password == null) {
                password = "";
            }
​
            username = username.trim();
            // 用戶信息的載體類,用來存儲及傳遞用戶名和用戶密碼
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 調用getAuthenticationManager()方法獲取AuthenticationManager實例
            // 調用獲取到的AuthenticationManager實例的authenticate()方法對封裝在authRequest 
            return this.getAuthenticationManager().authenticate(authRequest);
    }

3.4 RemeberMeAuthenticationFilter

當用戶沒有登錄直接訪問資源的時候,會從cookie中找不用戶信息,如果可以識別出cookie用戶則不需要填寫用戶名和密碼直接請求受保護的資源。首先會分析SecurityContext中有沒有Authentication對象,如果有則進入下一個過濾器,否則檢查request裏有沒有rememberMe的cookie信息,如果有則解析cookie,判斷用戶是否具有權限。

通過HttpSecurity調用rememverMe的相關方法進行配置。

               .rememberMe()
                    .tokenRepository(persistentTokenRepository()) // 配置token的持久化存儲
                    .tokenValiditySeconds(604800) //一週過期
                    .userDetailsService(userDetailsService()) //從數據庫拿到後使用這個做登錄
    /*
        配置token存到數據庫
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //建表語句 只需要使用一次 用後註釋掉
        tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

3.5 AnonymousAuthenticationFilter

匿名認證過濾器,Spring Security中所有資源的訪問都是有Authentication的,對於不需要通過UsernamePasswordAuthenticatioFilter的資源,會授予匿名用戶身份。可以通過HttpSecurity.anonymous()進行配置。

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        // 如果沒有授權
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
        }
​
        chain.doFilter(req, res);
    }
    protected Authentication createAuthentication(HttpServletRequest request) {
        AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(this.key, this.principal, this.authorities);
        auth.setDetails(this.authenticationDetailsSource.buildDetails(request));
        return auth;
    }

3.6 ExceptionTranslationFilter

實現了Spring Security的異常處理機制

  1. doFilter方法

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
​
        try {
            // 繼續調用後面的過濾器
            chain.doFilter(request, response);
            this.logger.debug("Chain processed normally");
        } catch (IOException var9) {
            throw var9;
        } catch (Exception var10) {
            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
            RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            // 判斷是否是AuthenticationException異常 不是則爲null
            if (ase == null) {
                ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }
            // 判斷是否是AccessDeniedException 不是則爲null
            if (ase == null) {
                if (var10 instanceof ServletException) {
                    throw (ServletException)var10;
                }
​
                if (var10 instanceof RuntimeException) {
                    throw (RuntimeException)var10;
                }
​
                throw new RuntimeException(var10);
            }
​
            if (response.isCommitted()) {
                throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
            }
            // 如果拋出的是AuthenticationException和AccessDeniedException則執行處理異常的方法
            this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
        }
​
    }
  1. handleSpringSecurityException用於處理異常

    private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
        // 如果是AuthenticationException則send出去
        if (exception instanceof AuthenticationException) {
            this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
            this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
            // 如果是AccessDeniedException異常且拿到的是匿名Token或記住我Token則send出去
        } else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
                this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception); // 否則 AccessDeniedHandler 用來處理授權異常 執行handle方法
                this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
            } else {
                this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
                this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
            }
        }
​
    }
  1. sendStartAuthentication方法

    protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
        SecurityContextHolder.getContext().setAuthentication((Authentication)null);
        this.requestCache.saveRequest(request, response);
        this.logger.debug("Calling Authentication entry point.");
        // AuthenticationEntryPoint 這個用來處理認證異常
        this.authenticationEntryPoint.commence(request, response, reason);
    }
  1. 認證異常的處理邏輯——commence方法

    當發生認證異常的時候,進行重定向,重定向到登錄頁面。

        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            String redirectUrl = null;
            if (this.useForward) {
                if (this.forceHttps && "http".equals(request.getScheme())) {
                    redirectUrl = this.buildHttpsRedirectUrlForRequest(request);
                }
    ​
                if (redirectUrl == null) {
                    String loginForm = this.determineUrlToUseForThisRequest(request, response, authException);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Server side forward to: " + loginForm);
                    }
    ​
                    RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
                    dispatcher.forward(request, response);
                    return;
                }
            } else {
                redirectUrl = this.buildRedirectUrlToLoginPage(request, response, authException);
            }
    ​
            this.redirectStrategy.sendRedirect(request, response, redirectUrl);
        }
  1. 授權異常的處理邏輯——handle方法

    服務端跳轉返回403

        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            if (!response.isCommitted()) {
                if (this.errorPage != null) {
                    request.setAttribute("SPRING_SECURITY_403_EXCEPTION", accessDeniedException);
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                    RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPage);
                    dispatcher.forward(request, response);
                } else {
                    response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
                }
            }
    ​
        }

3.7 FilterSecurityInterceptor

用於決定訪問特定路徑應該具備的權限,訪問的用戶的角色,權限是什麼,訪問的路徑需要什麼角色和權限。

  1. doFilter()方法

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 將請求上下文封裝爲一個FilterInvocation
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        // 調用該FilterInvocation執行安全認證
        this.invoke(fi);
    }
  1. invoke()方法

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        // 如果被指定爲請求中只執行一次且已經執行過直接放行進入下一個過濾器
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            // 如果被指定爲請求中只執行一次但未執行過 設置已執行標誌
            if (fi.getRequest() != null && this.observeOncePerRequest) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
            // 進行安全檢查(認證+授權)如果存在異常則進行異常處理 不繼續後續的過濾器
            InterceptorStatusToken token = super.beforeInvocation(fi);
​
            try {
                // 繼續執行後續的過濾器
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
​
            super.afterInvocation(token, (Object)null);
        }
​
    }
  1. beforeInvocation()方法

    protected InterceptorStatusToken beforeInvocation(Object object) {
            Assert.notNull(object, "Object was null");
            boolean debug = this.logger.isDebugEnabled();
            if (!this.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: " + this.getSecureObjectClass());
            } else {
                // 從安全配置中獲取安全元數據,記錄在 attributes
                Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
                if (attributes != null && !attributes.isEmpty()) {
                    if (debug) {
                        this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
                    }
    ​
                    if (SecurityContextHolder.getContext().getAuthentication() == null) {
                        this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
                    }
                    // 如果安全認證token存在,則檢查是否需要認證,如果需要,則執行認證並更行
            // 安全上下文中的安全認證token,如果認證失敗,拋出異常 AuthenticationException
                    Authentication authenticated = this.authenticateIfRequired();
    ​
                    try {
                        // 現在已經確保用戶通過了認證,現在基於登錄的當前用戶信息和目標資源的安全配置屬性
                        // 進行相應的權限檢查,如果檢查失敗,則拋出相應的異常 AccessDeniedException
                        this.accessDecisionManager.decide(authenticated, object, attributes);
                    } catch (AccessDeniedException var7) {
                        this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
                        throw var7;
                    }
    ​
                    if (debug) {
                        this.logger.debug("Authorization successful");
                    }
    ​
                    if (this.publishAuthorizationSuccess) {
                        this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                    }
    ​
                    Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                    if (runAs == null) {
                        if (debug) {
                            this.logger.debug("RunAsManager did not change Authentication object");
                        }
    ​
                        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                    } else {
                        if (debug) {
                            this.logger.debug("Switching to RunAs Authentication: " + runAs);
                        }
    ​
                        SecurityContext origCtx = SecurityContextHolder.getContext();
                        SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                        SecurityContextHolder.getContext().setAuthentication(runAs);
                        return new InterceptorStatusToken(origCtx, true, attributes, object);
                    }
                    // attributes==null說明該安全對象沒有配置安全控制,可以被公開訪問
                    // 如果系統配置了拒絕公開調用,則拋出異常拒絕當前請求
                } else if (this.rejectPublicInvocations) {
                    throw new IllegalArgumentException("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'");
                    // 系統允許公開調用,則不做安全檢查 返回null
                } else {
                    if (debug) {
                        this.logger.debug("Public object - authentication not attempted");
                    }
    ​
                    this.publishEvent(new PublicInvocationEvent(object));
                    return null;
                }
            }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章