- 更多的配置 (重寫configure方法)實現的自定義認證和受權(重寫用戶名密碼認證和模仿eladmin的token認證)和自定義受權
- Spring Security在SpringBoot中的自動裝配原理
- SecurityContext
ps:自定義受權,包含註解式開發和java-configure開發java
參考spring
Spring Security用戶認證和權限控制(默認實現)緩存
Spring Security用戶認證和權限控制(自定義實現)服務器
認證和鑑權
認證和鑑權分別對應authentication
/authorization
,Spring Security
和Shiro
都涉及到了這兩個詞ide
通俗點說spring-boot
-
認證就是登陸的過程。服務器根據你傳遞的用戶名,密碼或者其餘形式的參數查詢是否存在這個用戶,若是存在說明認證成功post
-
鑑權就是判斷你是否由權力訪問資源的流程。你想看某個視頻,只有vip用戶才能看,可是你當前登陸的帳戶不是vip,因此你不能看。判斷能不能看這個視頻(是否有權限訪問指定的資源)的過程就是鑑權ui
Spring Security的過濾器鏈
Spring Security
集成了Spring MVC
,Spring Security
經過建立一系列Filter
(過濾器)來實現認證和受權this
集成Spring Security
-
導入
Maven
依賴加密<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
建立
WebSecurityConfigurerAdapter
的子類並添加@EnableWebSecurity
註解@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { }
至此,啓動項目後,Spring Security
爲程序添加了許多過濾器用於認證和鑑權
Spring Security中內置的過濾器
Spring Security
中有許多內置的過濾器(這裏只簡單介紹兩個)
在嘗試自定義認證和受權流程以前應該先掌握內置的過濾器是如何工做的
其中我認爲比較重要的內置過濾器是UsernamePasswordAuthenticationFilter
和FilterSecurityInterceptor
(它是過濾器,不是攔截器)
Authentication
在瞭解過濾器以前先了解一下Authentication
類
此類是「認證主體」,這個類的包含了用戶是否定證成功,用戶的信息等信息
public interface Authentication extends Principal, Serializable { // 獲取權限 Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); // 返回認證主體是否已經被認證過 boolean isAuthenticated(); // 這隻認證主體是否成功經過認證 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
UsernamePasswordAuthenticationFilter
這個過濾器提供用戶名,密碼方式認證服務
這個過濾器攔截了/login
請求並校驗用戶名,密碼。若是認證成功(用戶名,密碼都對了)就建立一個Authentication
對象;認證失敗就拋AuthenticationException
- 默認只攔截
POST
方式的/login
請求,若是不符合直接執行下面的過濾器(chain.doFilter(request, response);
) - 獲取請求中的用戶名和密碼,並建立一個
Authentication
對象(實際類型是UsernamePasswordAhtienticationToken
),這個對象是沒有被認證的「認證主體」 - 使用成員變量
AuthenticationManager
的authenticate()
方法進行認證 AuthenticationManager
遍歷持有的AuthenticationProvider
對象,使用能認證此Ahthentication
的AuthenticationProvider
進行認證AbstractUserDetailsAuthenticationProvider
先使用緩存獲取user對象,若是獲取不到就用持有的UserDetialsService
和PasswordEncoder
從DB
中獲取User
對象並加密密碼再封裝起來- 拿到user對象後校驗對象是否過時,密碼是否正確等。沒問題後建立一個新的
AuthenticationToken
做爲已經被認證的「認證主體」 - 將
AuthenticationToken
放到SecurityContext
中(SecurityContext
之後再說)
上述流程中重要的部分是
- 過濾器自己和其認證邏輯
AuthenticationManager
對象。它持有各類AuthenticationProvider
,能夠完成對不一樣類型Authentication
的認證AuthenticationProvider
持有的UserDetialsService
和PasswordEncoder
。他們的做用是建立UserDetials
對象爲之後建立被認證的Authentication
作鋪墊
/* AbstractAuthenticationProcessingFilter 是UsernamePasswordAuthenticationFilter的父類 */ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 若是請求不符合要求就直接放過 if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } Authentication authResult; try { // 認證流程在這裏,若是認證失敗會拋異常 authResult = attemptAuthentication(request, response); if (authResult == null) { return; } } catch (AuthenticationException failed) { return; } if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } // 將Authentication放進SecurityContext中 successfulAuthentication(request, response, chain, authResult); } /* UsernamePasswordAuthenticationFilter */ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 若是請求不是POST方式就拋異常 if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); // 建立一個未認證的AuthenticationToken UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 調用AuthenticationManager進行認證 return this.getAuthenticationManager().authenticate(authRequest); } /* ProviderManager */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); Authentication result = null; // 遍歷持有的AuthenticationProvider,若是有AuthenticationProvider能認證Authentication就認證 for (AuthenticationProvider provider : getProviders()) { // AuthenticationProvider是否能認證當前類型的Authentication if (!provider.supports(toTest)) { continue; } try { // 認證 result = provider.authenticate(authentication); if (result != null) { break; } } catch (AuthenticationException e) { } } if (result != null) { return result; } } /* AbstractUserDetailsAuthenticationProvider */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); // 嘗試從緩存中獲取User對象 boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); // 緩存中獲取不到就從新獲取並緩存起來 if (user == null) { cacheWasUsed = false; try { // 調用UserDetailsService獲取user對象並使用PasswordEncoder加密密碼 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { } } try { // 前置檢查 preAuthenticationChecks.check(user); // 檢查密碼是否正確 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { } // 後置檢查。先後置檢查都在檢查user是否已通過期 postAuthenticationChecks.check(user); // 將user緩存起來 if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; // 建立一個被認證的Authentication(能執行到這裏還沒拋異常說明user認證成功了) return createSuccessAuthentication(principalToReturn, authentication, user); }
FilterSecurityInterceptor
這個過濾器被放在過濾器鏈的最後面,控制鑑權流程,用於檢查這次請求是否有權限訪問資源,若是不能就拋AccessException
- 從
SecurityContext
中獲取Authentication
對象,若是沒有認證就認證一下(調用AuthenticationManager
認證) - 獲取當前請求資源所須要的權限
- 調用
AccessDecisionManager
持有的投票器進行投票,根據投票結果斷定請求攜帶的Authentication
是否有權限訪問資源。斷定標準根據AccessDecisionManager
不一樣的實現類決定。例如一票否決,一票經過,多數服從少數等標準
上述流程中重要的部分是
- 過濾器自己
AccessDecisionManager
和投票器
ps:一般鑑權流程交給這個過濾器就足夠了,無需建立新的鑑權過濾器
AnonymousAuthenticationFilter
匿名的「權限主體」過濾器
- 若是你沒有通過認證就訪問被保護起來的資源,這個過濾器會檢測到
SecurityContext
中沒有Authentication
對象,併爲其建立一個AnonymousAuthenticationToken
類型的Authentication
並放進SecurityContext
- 匿名的「認證主體」只有
ROLE_ANONYMOUS
權限。在訪問被保護起來的資源時會被FilterSecurityInterceptor
斷定爲沒有權限訪問資源的