国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Spring Security內(nèi)置過濾器詳解

這篇具有很好參考價值的文章主要介紹了Spring Security內(nèi)置過濾器詳解。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

相關(guān)文章:

  1. OAuth2的定義和運行流程
  2. Spring Security OAuth實現(xiàn)Gitee快捷登錄
  3. Spring Security OAuth實現(xiàn)GitHub快捷登錄
  4. Spring Security的過濾器鏈機(jī)制
  5. Spring Security OAuth Client配置加載源碼分析

前言

根據(jù)前面的示例,我們已經(jīng)知道啟動時會加載18個過濾器,并且已經(jīng)知道了請求會匹配到DefaultSecurityFilterChain并依次通過這18個過濾器。

DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor

我們從OAuth2AuthorizationRequestRedirectFilter開始看,OAuth2開頭這非常明顯。

OAuth2AuthorizationRequestRedirectFilter

OAuth2 客戶端認(rèn)證核心過濾器,通過重定向到authorization_uri來獲取code
該過濾器并沒有doFilter()方法,只有doFilterInternal,而doFilter()存在于其父類OncePerRequestFilter過濾器中
如下是核心代碼:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    try {
        //從request、ClientRegistration中構(gòu)造出OAuth2的授權(quán)請求對象
        OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
        if (authorizationRequest != null) {
            //重定向到authorizationUri
            this.sendRedirectForAuthorization(request, response, authorizationRequest);
            return;
        }
    }
    catch (Exception ex) {
        this.unsuccessfulRedirectForAuthorization(request, response, ex);
        return;
    }
    try {
        //執(zhí)行下一個過濾器
        filterChain.doFilter(request, response);
    }
    catch (IOException ex) {
        throw ex;
    }
    catch (Exception ex) {
        //判斷是否有ClientAuthorizationRequiredException,如果有單獨處理
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
        ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
            .getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);
        if (authzEx != null) {
            try {
                OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request,
                                                                                                            authzEx.getClientRegistrationId());
                if (authorizationRequest == null) {
                    throw authzEx;
                }
                this.sendRedirectForAuthorization(request, response, authorizationRequest);
                this.requestCache.saveRequest(request, response);
            }
            catch (Exception failed) {
                this.unsuccessfulRedirectForAuthorization(request, response, failed);
            }
            return;
        }
        if (ex instanceof ServletException) {
            throw (ServletException) ex;
        }
        if (ex instanceof RuntimeException) {
            throw (RuntimeException) ex;
        }
        throw new RuntimeException(ex);
    }
}

OAuth2LoginAuthenticationFilter

說明:核心OAuth登錄過濾器,首先從URL中提取code,然后使用code獲取access_token,接著借助access_token獲取用戶信息,最終構(gòu)建出OAuth2AuthenticationToken認(rèn) 證對象,表明認(rèn)證成功。
該過濾器會在授權(quán)服務(wù)器調(diào)用回調(diào)接口的時候起作用,本例中回調(diào)的URL為/login/oauth2/code/gitee?code=c52e1e1f8ab954d680f4bb78e33ce303b15ed3cb1040bd47089067e9a8ed9ea5&state=k2Ci-0eyt0To1Me_ThgInRX2CPv2EtKSiu5xTrWyN3Y%3D
doFilter代碼位于父類AbstractAuthenticationProcessingFilter中,核心代碼如下:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    //通過requestMatcher判斷request請求是否需要處理
    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
        return;
    }
    try {
        //獲取身份認(rèn)證結(jié)果,并創(chuàng)建認(rèn)證對象,由子類實現(xiàn),OAuth2 使用的是OAuth2LoginAuthenticationFilter
        Authentication authenticationResult = attemptAuthentication(request, response);
        if (authenticationResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            return;
        }
        //對會話進(jìn)行處理,防止會話固定攻擊(session-fixation詳情可網(wǎng)上查詢)
        this.sessionStrategy.onAuthentication(authenticationResult, request, response);
        //認(rèn)證成功后,是否繼續(xù)執(zhí)行后面的過濾器
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //處理認(rèn)證成功后的處理邏輯,委托給AuthenticationSuccessHandler,此處為SavedRequestAwareAuthenticationSuccessHandler
        successfulAuthentication(request, response, chain, authenticationResult);
    }
    catch (InternalAuthenticationServiceException failed) {
        this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
        //認(rèn)證失敗處理
        unsuccessfulAuthentication(request, response, failed);
    }
    catch (AuthenticationException ex) {
        // Authentication failed
        //認(rèn)證失敗處理
        unsuccessfulAuthentication(request, response, ex);
    }
}

其中attemptAuthentication處的代碼量很大,作用是:獲取身份認(rèn)證結(jié)果,并創(chuàng)建認(rèn)證對象,由子類實現(xiàn),OAuth2 使用的是OAuth2LoginAuthenticationFilter,我們來看看這部分

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    throws AuthenticationException {
    //從授權(quán)服務(wù)器回調(diào)的URL請求中,將請求參數(shù)轉(zhuǎn)換為map格式,key=參數(shù)名,value=參數(shù)值
    MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
    //根據(jù)code、status、error字段判斷是否為返回請求
    if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
        OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    //返回OAuth2AuthorizationRequest,該對象在OAuth2LoginAuthenticationFilter中構(gòu)建
    //removeAuthorizationRequest官方的話是,但不太理解為什么要remove
    //Removes and returns the OAuth2AuthorizationRequest associated to the provided 
    //HttpServletRequest and HttpServletResponse or if not available returns null.
    OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
        .removeAuthorizationRequest(request, response);
    if (authorizationRequest == null) {
        OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    if (clientRegistration == null) {
        OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
                                                  "Client Registration not found with Id: " + registrationId, null);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    // @formatter:off
    String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
        .replaceQuery(null)
        .build()
        .toUriString();
    // @formatter:on
    //構(gòu)建OAuth2授權(quán)響應(yīng)對象
    OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
                                                                                                 redirectUri);
    //從request構(gòu)建一個完整的身份認(rèn)證信息
    Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
    //構(gòu)建OAuth2認(rèn)證Token
    OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
                                                                                              new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
    authenticationRequest.setDetails(authenticationDetails);
    //請求授權(quán)服務(wù)器的token-uri,進(jìn)行身份認(rèn)證,返回完整的OAuth2認(rèn)證Token
    //請求user-info-uri,獲取資源所有者主體信息
    //會輪詢所有的provider,直到遇到支持的provider,該處為OAuth2LoginAuthenticationProvider
    OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
        .getAuthenticationManager().authenticate(authenticationRequest);
    //轉(zhuǎn)換成需要返回的OAuth2AuthenticationToken
    OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
        .convert(authenticationResult);
    Assert.notNull(oauth2Authentication, "authentication result cannot be null");
    oauth2Authentication.setDetails(authenticationDetails);
    //構(gòu)建最終的OAuth2 授權(quán)客戶端
    OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
        authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
        authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
    //保存授權(quán)相關(guān)信息,此處保存到本地
    this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
    return oauth2Authentication;
}

DefaultLoginPageGeneratingFilter

說明:默認(rèn)的登錄頁面
當(dāng)沒有配置自定義登錄頁時,將使用該默認(rèn)登錄頁
如下的doFilter核心代碼:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    //當(dāng)前請求是否是failureUrl指定的地址
    boolean loginError = isErrorPage(request);
    //當(dāng)前請求是否是logoutSuccessUrl指定的地址
    boolean logoutSuccess = isLogoutSuccess(request);
    //是否是登錄請求,是否是重定向的錯誤請求,是否是登出地址
    if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
        //生成一個默認(rèn)的登錄頁
        String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
        response.setContentType("text/html;charset=UTF-8");
        response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
        response.getWriter().write(loginPageHtml);
        return;
    }
    //否則執(zhí)行下一個過濾器
    chain.doFilter(request, response);
}

DefaultLogoutPageGeneratingFilter

說明:默認(rèn)的登出頁面
當(dāng)請求地址為GET請求,/logout時,生成一個默認(rèn)的登出頁面
核心代碼如下:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    //地址是/logout
    if (this.matcher.matches(request)) {
        //生成一個登出頁面
        renderLogout(request, response);
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format("Did not render default logout page since request did not match [%s]",
                    this.matcher));
        }
        filterChain.doFilter(request, response);
    }
}

界面如下:
Spring Security內(nèi)置過濾器詳解

RequestCacheAwareFilter

說明:
從session中獲取SavedRequest,如果當(dāng)前請求信息和SaveRequest信息一致(一般是登錄成功后重定向),則返回SavedRequestAwareWrapper的HttpServletRequest包裝類

SecurityContextHolderAwareRequestFilter

說明:
通過HttpServletRequestFactory將HttpServletRequest請求包裝成SecurityContextHolderAwareRequestWrapper,它實現(xiàn)了HttpServletRequest,并進(jìn)行了擴(kuò)展,添加一些額外的方法,比如:getPrincipal()方法等。這樣就可以那些需要Principal等參數(shù)的Controller就可以接收到對應(yīng)參數(shù)了。除了這個地方的應(yīng)用,在其他地方,也可以直接調(diào)用request#getUserPrincipal()獲取對應(yīng)信息。

AnonymousAuthenticationFilter

說明:
匿名過濾器,如果執(zhí)行到該過濾器時還沒有主體,則創(chuàng)建一個匿名主體

OAuth2AuthorizationCodeGrantFilter

說明:
OAuth2授權(quán)碼授權(quán)過濾器,跟OAuth2LoginAuthenticationFilter很像,不知道在這里的作用是什么?
是為了兜底?

SessionManagementFilter

Session管理過濾器

ExceptionTranslationFilter

處理過濾器鏈中拋出的AccessDeniedException和AuthenticationException 異常

FilterSecurityInterceptor

對資源的過濾處理
核心代碼如下:

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    //該請求已經(jīng)運行過該過濾,跳過,執(zhí)行下一個過濾器
    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);
    }
    //獲取當(dāng)前的權(quán)限配置
    InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
    try {
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    }
    finally {
        //設(shè)置SecurityContextHolderStrategy context
        super.finallyInvocation(token);
    }
    //最后的處理
    super.afterInvocation(token, null);
}

獲取當(dāng)前 request 對應(yīng)的權(quán)限配置,首先是調(diào)用基類的 beforeInvocation 方法 。

看一下基類的 beforeInvocation 方法,從配置好的 SecurityMetadataSource 中獲取當(dāng)前 request 所對應(yīng)的 ConfigAttribute,即權(quán)限信息。

protected InterceptorStatusToken beforeInvocation(Object object) {
    ......
    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
        .getAttributes(object);
    if (attributes == null || attributes.isEmpty()) {
        if (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'");
        }
        ......
    }
}

這里需要注意一下 rejectPublicInvocations 屬性,默認(rèn)為 false。此屬性含義為拒絕公共請求。如果從配置好的 SecurityMetadataSource 中獲取不到當(dāng)前 request 所對應(yīng)的 ConfigAttribute 時,即認(rèn)為當(dāng)前請求為公共請求。如配置 rejectPublicInvocations 屬性為 true,則系統(tǒng)會拋出 IllegalArgumentException 異常,即當(dāng)前請求需要配置權(quán)限信息。

接下來,就要判斷是否需要進(jìn)行身份認(rèn)證了,即調(diào)用 authenticateIfRequired 方法。

protected InterceptorStatusToken beforeInvocation(Object object) {
    ......

    Authentication authenticated = authenticateIfRequired();

    ......
}

而判斷及身份認(rèn)證邏輯也并不復(fù)雜,首先會判斷當(dāng)前用戶是否已通過身份認(rèn)證,如果已通過身份認(rèn)證,則直接返回;如果尚未通過身份認(rèn)證,則調(diào)用身份認(rèn)證管理器 AuthenticationManager 進(jìn)行認(rèn)證,就如同登錄時一樣。認(rèn)證通過后,同樣會在當(dāng)前的安全上下文中存儲一份認(rèn)證后的 authentication。

private Authentication authenticateIfRequired() {
    Authentication authentication = SecurityContextHolder.getContext()
        .getAuthentication();
    if (authentication.isAuthenticated() && !alwaysReauthenticate) {
        if (logger.isDebugEnabled()) {
            logger.debug("Previously Authenticated: " + authentication);
        }
        return authentication;
    }
    authentication = authenticationManager.authenticate(authentication);
    // We don't authenticated.setAuthentication(true), because each provider should do
    // that
    if (logger.isDebugEnabled()) {
        logger.debug("Successfully Authenticated: " + authentication);
    }
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return authentication;
}

然后,使用獲取到的 ConfigAttribute ,繼續(xù)調(diào)用訪問控制器 AccessDecisionManager 對當(dāng)前請求進(jìn)行鑒權(quán)。

protected InterceptorStatusToken beforeInvocation(Object object) {
    ......
    // Attempt authorization
    try {
          this.accessDecisionManager.decide(authenticated, object, attributes);
        }
    catch (AccessDeniedException accessDeniedException) {
        publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                                                   accessDeniedException));
        throw accessDeniedException;
    }
    if (debug) {
        logger.debug("Authorization successful");
    }
    if (publishAuthorizationSuccess) {
        publishEvent(new AuthorizedEvent(object, attributes, authenticated));
    }
}

注意,無論鑒權(quán)通過或是不通后,Spring Security 框架均使用了觀察者模式,來通知其它Bean,當(dāng)前請求的鑒權(quán)結(jié)果。
如果鑒權(quán)不通過,則會拋出 AccessDeniedException 異常,即訪問受限,然后會被 ExceptionTranslationFilter 捕獲,最終解析后調(diào)轉(zhuǎn)到對應(yīng)的鑒權(quán)失敗頁面。

如果鑒權(quán)通過,AbstractSecurityInterceptor 通常會繼續(xù)請求。但是,在極少數(shù)情況下,用戶可能希望使用不同的 Authentication 來替換 SecurityContext 中的 Authentication。該身份認(rèn)證就會由 RunAsManager 來處理。這在某些業(yè)務(wù)場景下可能很有用,錄入服務(wù)層方法需要調(diào)用遠(yuǎn)程系統(tǒng)并呈現(xiàn)不同的身份。因為 Spring Security 會自動將安全標(biāo)識從一個服務(wù)器傳播到另一個服務(wù)器(假設(shè)使用的是正確配置的 RMI 或 HttpInvoker 遠(yuǎn)程協(xié)議客戶端),這就可能很有用。

在 AccessDecisionManager 鑒權(quán)成功后,將通過 RunAsManager 在現(xiàn)有 Authentication 基礎(chǔ)上構(gòu)建一個新的Authentication,如果新的 Authentication 不為空則將產(chǎn)生一個新的 SecurityContext,并把新產(chǎn)生的Authentication 存放在其中。這樣在請求受保護(hù)資源時從 SecurityContext中 獲取到的 Authentication 就是新產(chǎn)生的 Authentication。

protected InterceptorStatusToken beforeInvocation(Object object) {
    ......

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                                                            attributes);

    if (runAs == null) {
        if (debug) {
            logger.debug("RunAsManager did not change Authentication object");
        }

        // no further work post-invocation
        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                                          attributes, object);
    }
    else {
        if (debug) {
            logger.debug("Switching to RunAs Authentication: " + runAs);
        }

        SecurityContext origCtx = SecurityContextHolder.getContext();
        SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
        SecurityContextHolder.getContext().setAuthentication(runAs);

        // need to revert to token.Authenticated post-invocation
        return new InterceptorStatusToken(origCtx, true, attributes, object);
    }
}

注意,AbstractSecurityInterceptor 默認(rèn)持有的是 RunAsManager 的空實現(xiàn) NullRunAsManager。

public abstract class AbstractSecurityInterceptor implements InitializingBean,
    ApplicationEventPublisherAware, MessageSourceAware {
  ......
  private RunAsManager runAsManager = new NullRunAsManager();
    ......
 }

待請求完成后會在 finallyInvocation() 中將原來的 SecurityContext 重新設(shè)置給SecurityContextHolder。

protected void finallyInvocation(InterceptorStatusToken token) {
    if (token != null && token.isContextHolderRefreshRequired()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Reverting to original Authentication: "
                         + token.getSecurityContext().getAuthentication());
        }

        SecurityContextHolder.setContext(token.getSecurityContext());
    }
}

然而,無論正常調(diào)用,亦或是請求異常等,都會觸發(fā) finallyInvocation()。

public void invoke(FilterInvocation fi) throws IOException, ServletException {
    if ((fi.getRequest() != null)
       ......
    }
    else {
        ......

        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        finally {
            // 無論是否成功、拋異常與否,均會執(zhí)行
            super.finallyInvocation(token);
        }

        // 正常請求結(jié)束,最后也會執(zhí)行(afterInvocation 內(nèi)部會調(diào)用finallyInvocation )
        super.afterInvocation(token, null);
    }
}

即便是正常執(zhí)行結(jié)束,依然會執(zhí)行 finallyInvocation()(afterInvocation 內(nèi)部會調(diào)用finallyInvocation )。

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
    ......

    finallyInvocation(token); // continue to clean in this method for passivity

    ......
}

此外,Spring Security 對 RunAsManager 有一個還有一個非空實現(xiàn)類 RunAsManagerImpl,其構(gòu)造新 Authentication 的邏輯如下:

如果受保護(hù)對象對應(yīng)的 ConfigAttribute 中擁有以“RUN_AS_”開頭的配置屬性,則在該屬性前加上“ROLE_”,然后再把它作為一個 SimpleGrantedAuthority 賦給將要創(chuàng)建的 Authentication(如ConfigAttribute 中擁有一個“RUN_AS_ADMIN”的屬性,則將構(gòu)建一個“ROLE_RUN_AS_ADMIN”的SimpleGrantedAuthority),最后再利用原 Authentication 的 principal、權(quán)限等信息構(gòu)建一個新的 Authentication 并返回;如果不存在任何以“RUN_AS_”開頭的 ConfigAttribute,則直接返回null。

public Authentication buildRunAs(Authentication authentication, Object object,
      Collection<ConfigAttribute> attributes) {
    List<GrantedAuthority> newAuthorities = new ArrayList<>();

    for (ConfigAttribute attribute : attributes) {
        if (this.supports(attribute)) {
            GrantedAuthority extraAuthority = new SimpleGrantedAuthority(
                getRolePrefix() + attribute.getAttribute());
            newAuthorities.add(extraAuthority);
        }
    }

    if (newAuthorities.size() == 0) {
        return null;
    }

    // Add existing authorities
    newAuthorities.addAll(authentication.getAuthorities());

    return new RunAsUserToken(this.key, authentication.getPrincipal(),
                              authentication.getCredentials(), newAuthorities,
                              authentication.getClass());
}

AccessDecisionManager 是在訪問受保護(hù)的對象之前判斷用戶是否擁有該對象的訪問權(quán)限。然而,有時候我們可能會希望在請求執(zhí)行完成后對返回值做一些修改或者權(quán)限校驗,當(dāng)然,也可以簡單的通過AOP來實現(xiàn)這一功能。

同樣的,Spring Security 提供了 AfterInvocationManager 接口,它允許我們在受保護(hù)對象訪問完成后對返回值進(jìn)行修改或者進(jìn)行權(quán)限校驗,權(quán)限校驗不通過時拋出 AccessDeniedException,并使用觀察者模式通知其它Bean。

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
    ......

    if (afterInvocationManager != null) {
        ......
        catch (AccessDeniedException accessDeniedException) {
            AuthorizationFailureEvent event = new AuthorizationFailureEvent(
                token.getSecureObject(), token.getAttributes(), token
                .getSecurityContext().getAuthentication(),
                accessDeniedException);
            publishEvent(event);

            throw accessDeniedException;
        }
    }
  ......  
}

其將由 AbstractSecurityInterceptor 的子類進(jìn)行調(diào)用,如默認(rèn)子類 FilterSecurityInterceptor 。

需要特別注意的是,AfterInvocationManager 需要在受保護(hù)對象成功被訪問后才能執(zhí)行。

類似于AuthenticationManager,AfterInvocationManager 同樣也有一個默認(rèn)的實現(xiàn)類AfterInvocationProviderManager,其中有一個由 AfterInvocationProvider 組成的集合屬性。

public class AfterInvocationProviderManager implements AfterInvocationManager,
    InitializingBean {
  ......

  private List<AfterInvocationProvider> providers;

    ......
}

非常有趣的是,AfterInvocationProvider 與 AfterInvocationManager 具有相同的方法定義。此一來,在調(diào)用AfterInvocationProviderManager 中的方法時,實際上就是依次調(diào)用其中成員屬性 providers 中的AfterInvocationProvider 接口對應(yīng)的方法。

public Object decide(Authentication authentication, Object object,
                     Collection<ConfigAttribute> config, Object returnedObject)
    throws AccessDeniedException {
    Object result = returnedObject;
    for (AfterInvocationProvider provider : providers) {
        result = provider.decide(authentication, object, config, result);
    }
    return result;
}

而 AfterInvocationProvider 的默認(rèn)實現(xiàn)類 PostInvocationAdviceProvider 中的 PostInvocationAuthorizationAdvice,其默認(rèn)實現(xiàn)類 ExpressionBasedPostInvocationAdvice,不正是對應(yīng)著后置權(quán)限注解 @PostAuthorize 嗎?

最后,關(guān)于 FILTER_APPLIED 常量,在 FilterSecurityInterceptor 中是這么使用的:

public void invoke(FilterInvocation fi) throws IOException, ServletException {
    if ((fi.getRequest() != null)
        && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
        && observeOncePerRequest) {
        // filter already applied to this request and user wants us to observe
        // once-per-request handling, so don't re-do security checking
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    }
    else {
        // first time this request being called, so perform security checking
        if (fi.getRequest() != null && observeOncePerRequest) {
            fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
        }

        ......
    }
}

其主要作用,是用于阻止請求的重復(fù)安全檢查。

原理也簡單,第一次執(zhí)行時,檢查 request 中 FILTER_APPLIED 屬性值為空,則放入值;后續(xù)該 request 再次請求時,F(xiàn)ILTER_APPLIED 屬性值不為空,代表已經(jīng)進(jìn)行過安全檢查,則該請求直接通過,不再重復(fù)進(jìn)行安全檢查。文章來源地址http://www.zghlxwxcb.cn/news/detail-452387.html

到了這里,關(guān)于Spring Security內(nèi)置過濾器詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【深入淺出Spring Security(五)】自定義過濾器進(jìn)行前后端登錄認(rèn)證

    【深入淺出Spring Security(五)】自定義過濾器進(jìn)行前后端登錄認(rèn)證

    在【深入淺出Spring Security(二)】Spring Security的實現(xiàn)原理 中小編闡述了默認(rèn)加載的過濾器,里面有些過濾器有時并不能滿足開發(fā)中的實際需求,這個時候就需要我們自定義過濾器,然后填入或者替換掉原先存在的過濾器。 首先闡述一下添加過濾器的四個方法(都是 HttpSecur

    2024年02月08日
    瀏覽(25)
  • Spring Cloud Gateway 過濾器詳解

    Spring Cloud Gateway 過濾器詳解

    Spring Cloud Gateway根據(jù)作用范圍劃分為:GatewayFilter和GlobalFilter 由filter工作流程點,可以知道filter有著非常重要的作用,在“pre”類型的過濾器可以做參數(shù)校驗、權(quán)限校驗、流量監(jiān)控、日志輸出、協(xié)議轉(zhuǎn)換等,在“post”類型的過濾器中可以做響應(yīng)內(nèi)容、響應(yīng)頭的修改,日志的輸

    2023年04月08日
    瀏覽(24)
  • Spring Cloud Gateway過濾器GlobalFilter詳解

    Spring Cloud Gateway過濾器GlobalFilter詳解

    一、過濾器的場景 在springCloud架構(gòu)中,網(wǎng)關(guān)是必不可少的組件,它用于服務(wù)路由的轉(zhuǎn)發(fā)。對客戶端進(jìn)行屏蔽微服務(wù)的具體細(xì)節(jié),客戶端只需要和網(wǎng)關(guān)進(jìn)行交互。所以網(wǎng)關(guān)顧名思義,就是網(wǎng)絡(luò)的一個關(guān)卡。它就是一座城的城門守衛(wèi)。所以這個守衛(wèi)就可以做很多工作,比如對來訪

    2024年02月14日
    瀏覽(16)
  • SpringCloudGateway--過濾器(內(nèi)置filter)

    SpringCloudGateway--過濾器(內(nèi)置filter)

    目錄 一、概覽 二、內(nèi)置過濾器 1、StripPrefix 2、AddRequestHeader 3、AddResponseHeader 4、DedupeResponseHeader 5、AddRequestParameter 6、CircuitBreaker 7、FallbackHeaders 8、RequestRateLimiter 9、RedirectTo 10、RemoveRequestHeader 11、RemoveResponseHeader 12、RemoveRequestParameter 13、RewritePath? 14、RewriteResponseHeader? 15、S

    2024年02月01日
    瀏覽(28)
  • Spring Cloud Gateway GlobalFilter(全局過濾器)詳解(官方原版)

    GlobalFilter接口具有與GatewayFilter相同的簽名。這些是有條件地應(yīng)用于所有路由的特殊過濾器。 當(dāng)請求與路由匹配時,過濾web處理程序會將GlobalFilter的所有實例和GatewayFilter的所有路由特定實例添加到過濾器鏈中。這個組合過濾器鏈由org.springframework.core.Ordered接口排序,您可以通

    2024年02月09日
    瀏覽(16)
  • 【Spring】Springboot過濾器Filter和攔截器Inteceptor詳解及使用場景

    Springboot過濾器Filter和攔截器Inteceptor詳解及使用場景

    2024年02月13日
    瀏覽(35)
  • [后端開發(fā)] 過濾器相關(guān)注解

    使用Springboot框架開發(fā)后端,在鑒權(quán)的時候使用到了過濾器。但是在測試的過程發(fā)現(xiàn),跨域的過濾器在過濾鏈中出現(xiàn)了兩次,導(dǎo)致前端訪問后端接口時報錯:The \\\'Access-Control-Allow-Origin\\\' headers contains multiple values,but only one allowed.錯誤 在瀏覽器端比較正常訪問接口和報錯接口的hea

    2024年04月16日
    瀏覽(22)
  • Vue2-收集表單數(shù)據(jù)、過濾器、內(nèi)置指令與自定義指令、Vue生命周期

    Vue2-收集表單數(shù)據(jù)、過濾器、內(nèi)置指令與自定義指令、Vue生命周期

    ??:我徒越萬重山 千帆過 萬木自逢春 更多Vue知識請點擊——Vue.js 1、不同標(biāo)簽的value屬性 若: input type=\\\"text\\\"/ 普通輸入框,則v-model收集的是value值,用戶輸入的就是value值。 若: input type=\\\"radio\\\"/ 單選框,則v-model收集的是value值,且要給標(biāo)簽配置value值。 若: input type=\\\"checkb

    2024年02月13日
    瀏覽(55)
  • 【C++】位圖與布隆過濾器(內(nèi)含相關(guān)高頻面試題)

    【C++】位圖與布隆過濾器(內(nèi)含相關(guān)高頻面試題)

    ? 本篇文章會對 位圖和布隆過濾器進(jìn)行詳解 。同時還會給出 位圖和布隆過濾器相關(guān)的高頻面試題與解答 。希望本篇文章會對你有所幫助。? 文章目錄 一、位圖的引入? 1、1 查找整數(shù)(騰訊面試題) 1、2 解決方法1 1、3?解決方法2? ?1、3、1 外部排序 二、位圖的原理與實現(xiàn)

    2024年02月12日
    瀏覽(23)
  • spring boot過濾器實現(xiàn)項目內(nèi)接口過濾

    spring boot過濾器實現(xiàn)項目內(nèi)接口過濾

    由于業(yè)務(wù)需求,存在兩套項目,一套是路由中心,一套是業(yè)務(wù)系統(tǒng). 現(xiàn)在存在問題是,路由中心集成了微信公眾號與小程序模塊功能,業(yè)務(wù)系統(tǒng)部署了多套服務(wù). 現(xiàn)在需要通過調(diào)用路由中心將接口重新路由到指定的業(yè)務(wù)系統(tǒng)中 將小程序,公眾號用戶信息與業(yè)務(wù)系統(tǒng)做綁定 將路由中心的

    2023年04月20日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包