1. 程式人生 > 其它 >Spring-security原始碼-Filter之FilterSecurityInterceptor(十八)

Spring-security原始碼-Filter之FilterSecurityInterceptor(十八)

FilterSecurityInterceptor最後一個過濾器,主要做認證和授權攔截,比如我們未登入訪問需要登入的頁面或者我們配置了授權的頁面

http.authorizeRequests() .antMatchers("/hello").hasRole("admin").antMatchers("/hello2").hasAnyAuthority("au-test")

初始化處
org.springframework.security.config.annotation.web.configurers.AbstractInterceptUrlConfigurer#configure

  @Override
    
public void configure(H http) throws Exception { /** * 呼叫子類獲取FilterInvocationSecurityMetadataSource * 主要是為了管理我們配置 url對映 對應的配置比如antMatchers("/hello").hasRole("admin") * 後續就可以快速通過url獲取對應的匹配配置 */ FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) { return; } //建立FilterSecurityInterceptor FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(http, metadataSource, http.getSharedObject(AuthenticationManager.class)); if (this.filterSecurityInterceptorOncePerRequest != null
) { securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest); } //注入 securityInterceptor = postProcess(securityInterceptor); //加入到Fitler http.addFilter(securityInterceptor); http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); }

org.springframework.security.web.access.intercept.FilterSecurityInterceptor#doFilter

  @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        //<1>通過FilterInvocation封裝
        invoke(new FilterInvocation(request, response, chain));
    }

<1>

org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke

  public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        //是否需要驗證許可權和會話
        if (isApplied(filterInvocation) && this.observeOncePerRequest) {
            // filter already applied to this request and user wants us to observe
            // once-per-request handling, so don't re-do security checking
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
            return;
        }
        // first time this request being called, so perform security checking
        if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
            filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
        }
        //<2>呼叫DispatcherFilter之前驗證
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
        try {
            //繼續放行 因為和這個是最後一個過濾器 所以我們看 放行的過濾器<5>
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        }
        finally {
            super.finallyInvocation(token);
        }
        //呼叫Dispatcher之後驗證
        super.afterInvocation(token, null);
    }

<2>


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

 /**
     * Object封裝了我們的Url
     * @param object
     * @return
     */
    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());
        }
        //根據Url獲取ConfigAttribute 就是我們配置的url的hasRole的執行是資料結構 通過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
        }
        //如果沒有使用者資訊拋錯由ExceptionTranslationFilter 捕獲處理
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"), object, attributes);
        }
        //<3>有使用者資訊 校驗是否認證通過
        Authentication authenticated = authenticateIfRequired();
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
        }
        //<4>根據許可權配置 進行許可權校驗
        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));
        }

        // A許可權通過的處理器 我們應該可以根據這個重新設定使用者資訊
        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);

    }

<3>

 private Authentication authenticateIfRequired() {
        //獲得使用者資訊
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication));
            }
            return authentication;
        }
        //<4>如果未認證則交給authenticationManager 重新認證
        authentication = this.authenticationManager.authenticate(authentication);
        // Don't authenticated.setAuthentication(true) because each provider does that
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication));
        }
        //set到Authentication
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return authentication;
    }

<4>

    private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
                                      Authentication authenticated) {
        try {
            //交給accessDecisionManager  感興趣可以打斷點繼續檢視比如通過eval 表示式 或者對應的處理類獲取Authentication的許可權或則判斷 
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException ex) {//許可權認證未通過丟擲異常 由ExceptionTranslationFilter 捕獲處理
            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;
        }
    }

<5>

可以參考https://www.cnblogs.com/LQBlog/p/15508248.html#autoid-11-0-0

secrityFilter最終由FilterChainProxy封裝作為入口

@Override
    protected Filter performBuild() throws Exception {
        Assert.state(!this.securityFilterChainBuilders.isEmpty(),
                () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
                        + "Typically this is done by exposing a SecurityFilterChain bean "
                        + "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
                        + "More advanced users can invoke " + WebSecurity.class.getSimpleName()
                        + ".addSecurityFilterChainBuilder directly");
        /**
         * 得我們設定的忽略檢查為他們新增一個filter
         *  public void configure(WebSecurity web) throws Exception {
         *         web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
         *     }
         */
        int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
        List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
        for (RequestMatcher ignoredRequest : this.ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        //securityFilterChainBuilders為HttpSecurity<6>處初始化
        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
            //執行build<1> 最終會構建成<13>
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        //通過FilterChainProxy 代理管理 它實現了ServletFilter 通過FilterChainProxy為Servlet入口 進入security的自己的filter
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (this.httpFirewall != null) {
            filterChainProxy.setFirewall(this.httpFirewall);
        }
        if (this.requestRejectedHandler != null) {
            filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
        }
        filterChainProxy.afterPropertiesSet();

        Filter result = filterChainProxy;
        if (this.debugEnabled) {
           
            result = new DebugFilter(filterChainProxy);
        }
        this.postBuildAction.run();
        //返回filter 我們請求都會到filterChainProxy  通過他呼叫security的filter實現securityfilter 注入邏輯
        return result;
    }

FilterChainProxy的doFilter最終是委託給VirtualFilterChain

org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter

 @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        //表示是最後一個Filter
        if (this.currentPosition == this.size) {
            if (logger.isDebugEnabled()) {
                logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
            }
            // Deactivate path stripping as we exit the security filter chain
            this.firewalledRequest.reset();
            //originalChain 就是我們的DispatcherFilter
            this.originalChain.doFilter(request, response);
            return;
        }
        //不是最後一個則依次++遍歷所有Filter
        this.currentPosition++;
        Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
                    this.currentPosition, this.size));
        }
        nextFilter.doFilter(request, response, this);
    }