Spring之Spring Security基本原理

前面博客所述,Spring Security是通過自定義的Filter對相關的URL進行權限控制,這些個filter組合起來通過兩個過程對權限進行了控制,認證(authentication)授權(authorization)。認證是來識別當前用戶是誰的過程,授權是判斷當前用戶是否有權限進行相關操作的過程。

認證(authentication)

認證的過程相對簡單,基本都是判斷當前正在操作的用戶(Principal)和密碼(credentials)是否匹配。對與簡單登錄的方式,就是去匹配用戶名和密碼;對于單點登錄,可能就是去驗證token是否有效了。獲取到這些信息后,會包裝到對象Authentication中。這個Authentication就是后續(xù)Filter決定頁面跳轉的依據。Authentication定義如下:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
授權(authorization)

對于需要控制權限的URL,一般都要三部分信息:

  1. URL的pattern,即對哪些URL進行權限控制
  2. 設置權限,即最低需要什么權限才能訪問這些URL
  3. 當前用戶是什么權限
獲取用戶權限

前兩點可以通過配置<security:intercept-url pattern="/abc/**" access="hasRole('USER')"/>指定,并被包裝成SecurityMetadataSource對象,那么當前登錄用戶的權限從哪里獲???

Spring Security定義了如下接口, UserDetailsService用于獲取UserDetail,而UserDetail里包含權限。

public interface UserDetailsService {
    /**
     * Locates the user based on the username. In the actual implementation, the search
     * may possibly be case sensitive, or case insensitive depending on how the
     * implementation instance is configured. In this case, the <code>UserDetails</code>
     * object that comes back may have a username that is of a different case than what
     * was actually requested...
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
    /**
     * Returns the authorities granted to the user. Cannot return null.
     */
    Collection<? extends GrantedAuthority> getAuthorities();
    ......
}

具體的獲取UserDetail的過程,要根據具體的業(yè)務進行處理,比如從數(shù)據庫獲取,或者從緩存中得到。獲取之后包裝為Authentication對象供后續(xù)Filter使用。

關鍵的filter

AbstractAuthenticationProcessingFilter
用于認證,其步驟如下:

  1. 從request獲取相關用戶信息(密碼、token等)構造Authentication;
  2. AuthenticationManager其中的AuthenticationProvider進行Authentication驗證;Provider里可能會獲取UserDetails(包含用戶具有的權限)放入Authentication中;若驗證通過,則返回該Authentication中;否則拋異常;
  3. 該Filter捕獲到異常,則導航到相關error或login頁面;若正常,則根據具體的代碼設置,由后面的filter繼續(xù)處理或直接訪問到資源;
        // Authentication success. 上面第三步
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }

        successfulAuthentication(request, response, chain, authResult);

當然,這個filter并不是每個url都會去攔截的,只有滿足一定條件的url才會去攔截。比如不需要權限控制的url就不會被攔截。

FilterSecurityInterceptor和ExceptionTranslationFilter
這兩個filter用于授權。其在Spring Security的Filter Chain中處于很靠后的位置。
FilterSecurityInterceptor的關鍵代碼如下:

        //獲取Authentication
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            //這里拋出去的異常會由ExceptionTranslationFilter捕獲
            throw accessDeniedException;
        }

ExceptionTranslationFilter關鍵代碼如下:

       try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            // Try to extract a SpringSecurityException from the stacktrace
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
            // Rethrow ServletExceptions and RuntimeExceptions as-is
                ........
            }
        }

ExceptionTranslationFilter會根據拋出的異常是AccessDeniedException還是AuthenticationException來判斷導航頁面。

在Spring Security所有的Filter中,只有兩種filter是可以導航到頁面的:XXXAuthenticationProcessingFilter和ExceptionTranslationFilter,因此在配置時,需要提供相關頁面url給這兩種filter。

常用的幾個Filter

1. SecurityContextPersistenceFilter

該filter默認啟用,作用是保存Authentication對象使后續(xù)的filter可以獲得這個對象。對于同一個用戶(session id相同),會從Session中取出用戶的校驗結果。若是第一次訪問,則會新建SecurityContext放入SecurityContextHolder(該Holder實際上是用一個ThreadLocal來保存SecurityContext)待后續(xù)代碼保存校驗結果。

2. CasAuthenticationFilter

用于處理CAS service的token,對于用到單點登錄的系統(tǒng),這個filter會經常使用。

3. AnonymousAuthenticationFilter

默認啟用。由上文可知,AbstractAuthenticationProcessingFilter中可以構造Authentication。那么不需要權限控制的url怎么去構造Authentication呢?AnonymousAuthenticationFilter就發(fā)揮了作用。該filter構造了一個pesudo Authentication提供給FilterSecurityInterceptor,從而使所有的url的處理流程統(tǒng)一。

基本步驟

flow.png

上圖包括了最基本的驗證流程,當然默認還有很多filter會起作用,圖中并沒有顯示。其中AbstractAuthenticationProcessingFilter并不是必需的,其他的四個都會默認啟用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容