前言:本文會結合相應的代碼示例及框架源碼。
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默認生成的登陸頁安全
而後輸入剛纔咱們的用戶名("baozi")與密碼("888");springboot
簡單幾步咱們就實現了登陸功能,那其中發生了什麼呢?讓咱們先看一張流程圖(精簡版);
其實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,好了今天的分享就到這裏:-)