spring security 源碼分析(1)

前言:本文會結合相應的代碼示例及框架源碼。

Spring Security是一個安全框架,側重於爲Java應用程序提供身份驗證和受權。 Pivotal 團隊的背書,再加上如今主流web開發是圍繞着Spring框架的其優點相較於其餘安全框架優點不言而喻🙃。廢話就很少說了,然咱們開始吧。web

首先導入相關依賴(tip:做者開發使用的框架是SpringBoot)spring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

接下來是代碼api

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 當前版本是須要配置編碼器的
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //開啓身份認證任何請求,任何請求都須要有基本ROLE_USER身份
        //用戶還能夠進行更細粒度的控制。
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        //開啓表單登陸
        http.formLogin();

    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用內存用戶信息認證
        auth.inMemoryAuthentication().withUser("baozi")
                //對咱們的明文密碼進行加密
                .password(passwordEncoder.encode("888"))
                .roles("USER")
                .and()
                .passwordEncoder(passwordEncoder);
    }
}

而後啓動咱們的springboot應用,因爲我配置的是anyrequest(),第一次請求沒有進行過身邊驗證因此它會跳轉到springboot默認生成的登陸頁安全

clipboard.png

而後輸入剛纔咱們的用戶名("baozi")與密碼("888");springboot

clipboard.png
簡單幾步咱們就實現了登陸功能,那其中發生了什麼呢?讓咱們先看一張流程圖(精簡版);
clipboard.png
其實springsecurity的核心就是一組過濾器。下面列出的三個過濾器是此次demo涉及到的主要過濾器。我將結合部分源碼解讀。(tip:方法參數及一些不那麼重要的代碼我省略了)mvc

首先用戶請求咱們的受保護資源,第一次用戶請求時沒有帶任何信息的,因此用戶的請求會直接到最後一個攔截器FilterSecurityInterceptor進行訪問權限處理。框架

// FilterSecurityInterceptor dofilter()中執行invoke()
public void invoke(){
    ......
    // 這裏調用父類的方法進行鑑權
    InterceptorStatusToken token = super.beforeInvocation(fi);
    
    // 這裏實際已經調用了咱們的受保護資源
    fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    ......
}

//讓咱們展開父類AbstractSecurityInterceptor的beforeInvocation()方法
//tip:該方法內會根據處理結果發佈不一樣的事件,若是你想作日誌的話,能夠以此爲拓展。
protected InterceptorStatusToken beforeInvocation(){
    ......
    try{
        //決策是否有權限訪問咱們的資源,具體在上面SecurityConfig.configure(HttpSecurity http)
        //中配置決策管理器決策失敗拋出異常
        this.accessDecisionManager.decide(authenticated, object, attributes);
    }
    catch (AccessDeniedException accessDeniedException) {
        publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                accessDeniedException));
       
        throw accessDeniedException;
    }
    ......
}

異常逐級拋出,最後由ExceptionTranslationFilter捕獲。此過濾器將嘗試解析異常SpringSecurityException
異常,解析失敗直接拋出,最終由springmvc的異常攔截器捕獲處理。解析成功將根據不一樣的訪問失敗緣由返回。ide

public void doFilter(){
        
    try{
        chain.doFilter(request, response);
    }catch(){
     //io異常繼續拋出
    }
    catch(){
        ......
        //上面省略代碼是嘗試將異常轉換爲SpringSecurityException 成功後將進行。
        if(ase != null){
              ......
              handleSpringSecurityException(request, response, chain, ase);
        }
    }
    //非SpringSecurityException繼續拋出
    ......

 }
 //此處將根據異常類型或異常信息返回不一樣的頁面 sendStartAuthentication()負責;
private void handleSpringSecurityException(){
    //跳轉到登陸頁。
    ex instance AuthenticationException
        sendStartAuthentication(......)
    // 分爲匿名用戶 跳轉到登陸頁
    // 與 非匿名用戶(被服務端主動拒絕的用戶) accessDeniedHandler執行handle();
    //默認的accessDeniedHandler是返回403頁面,咱們也能夠本身實現accessDeniedHandle接口
    //自定義返回
    ex instance AuthenticationException
        sendStartAuthentication(......) or accessDeniedHandle.handle()
}

而後咱們被引導到了登陸頁(本次不考慮非匿名用戶被拒絕的狀況),此次咱們登陸將帶上username password;
還有請求的是/login post方法(tip:可自定義),這個請求將被AbstractAuthenticationProcessingFilter::UsernamePasswordAuthenticationFilter過濾器進行處理。spring-boot

public void doFilter(){
    ......
    //抽象方法交由子類實現,本例爲UsernamePasswordAuthenticationFilter
    //內部進行相應的用戶驗證,成功則將Authentication加入安全上下文。
    authResult = attemptAuthentication(request, response);
    
    ......
    //處理失敗跳轉到失敗的頁面,可自定義
    unsuccessfulAuthentication(request, response, failed);

    ......
    
    //處理成功轉發到原請求handler,可自定義
    successfulAuthentication(request, response, chain, authResult);
}
}

到此登陸處理流程就完成了。post

總結一下

springsecurity將簡單易用的api暴露給咱們,但的"複雜"的流程封裝在其中.可能第一次接觸的開發者有點懵,可是歸根結底只是一組過濾器,弄明白順序,這也是往後咱們"把玩"這個框架的最大前提。下面我列出了主要的過濾器及順序,從上到下依次執行。

ChannelProcessingFilter
ConcurrentSessionFilter
SecurityContextPersistenceFilter
LogoutFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
UsernamePasswordAuthenticationFilter
ConcurrentSessionFilter
OpenIDAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter

根據不一樣的狀況加載的過濾器也有所不一樣,具體可觀察控制檯,會有相應的日誌報告。

下節我將主要講解與分析被我一筆帶過的AbstractAuthenticationProcessingFilter,好了今天的分享就到這裏:-)

相關文章
相關標籤/搜索