在試題系統開發過程當中,認證方式愈來愈完善,也對Spring Security
有了更加深入的理解。前端
本文,咱們一塊兒來領略Spring Security
的設計原理。java
Servlet
是Java Web
領域中的軟件開發規範,Tomcat
是實現Servlet
規範的Java Web
服務器。安全
package javax.servlet; public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
Servlet
長這樣,生命週期十分簡單,初始化init
、業務邏輯service
、銷燬destroy
。服務器
簡單來講:咱們根據Servlet
接口開發咱們的應用,服務器開發商根據Servlet
接口開發Servlet
服務器。架構
前端HTTP
請求,Tomcat
選擇路由匹配的Servlet
,若是已經實例化,直接調用service
;若是未實例化,實例化後調用service
,處理完以後返回響應。併發
圖中示例的url
統一採用小寫,關於url
大小寫的問題,如下是結論,雖然有些url
是大小寫不敏感的,可是這使得表示惟一性變得困難,咱們一般應該認爲大小寫是敏感的。框架
There may be URLs, or parts of URLs, where case doesn't matter, but identifying these may not be easy. Users should always consider that URLs are case-sensitive.
爲了在Servlet
先後執行其餘邏輯,定義了Filter
接口,在請求先後都會執行。ide
從Spring Security
官方文檔摘抄的一張圖:高併發
Spring Security
的原理其實就是經過Servlet
中的Filter
技術進行實現的,經過一系列內置的或自定義的安全Filter
,實現接口認證與受權。性能
Spring Security
官方也給出了默認Filter
的執行順序,先執行認證的過濾器,後執行受權的過濾器,其中就有咱們經常使用的UsernamePasswordAuthenticationFilter
、BasicAuthenticationFilter
等等。
閱讀源碼一個Filter
源碼便可理解整個認證架構設計。
注:如下代碼中部分無關代碼已被刪減。
public class BasicAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { /** 從請求中獲取用戶名密碼信息 */ UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request); /** 若是沒有相關信息,說明不是此種認證方式,執行後續過濾器 */ if (authRequest == null) { chain.doFilter(request, response); return; } /** 獲取用戶名 */ String username = authRequest.getName(); /** 判斷該用戶是否須要認證 */ if (authenticationIsRequired(username)) { /** 嘗試使用 token 進行認證 */ Authentication authResult = this.authenticationManager .authenticate(authRequest); /** 認證成功,將認證結果置入上下文 */ SecurityContextHolder.getContext().setAuthentication(authResult); /** 認證成功相關回調 */ this.rememberMeServices.loginSuccess(request, response, authResult); onSuccessfulAuthentication(request, response, authResult); } } catch (AuthenticationException failed) { /** 認證失敗,清空當前上下文信息 */ SecurityContextHolder.clearContext(); /** 認證失敗相關回調 */ this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, failed); /** 若是須要忽略失敗,則繼續執行後續過濾器 */ if (this.ignoreFailure) { chain.doFilter(request, response); } /** 不然開始執行新的認證方案 */ else { this.authenticationEntryPoint.commence(request, response, failed); } return; } /** 本過濾器執行完畢,執行後續過濾器 */ chain.doFilter(request, response); } }
其實很簡單是否是?
若是默認的驗證方式不知足要求,要怎麼添加自定義驗證方式呢?其實只須要添加自定義的Filter
便可。
就好比說常見的短信驗證碼登陸方式:
默認不支持短信方式,咱們能夠在過濾器鏈中植入一個自定義的短信驗證過濾器,認證成功後設置認證信息便可。
SecurityContextHolder.getContext().setAuthentication(authResult);
這個是上個月遇到的問題,嘗試了一下OAuth 2.0
認證架構。
其實這個架構很廣泛,許多項目都採用該種架構。只不過都是採用Spring Cloud Netflix Zuul + Spring Security Resource Server
的實現。
這裏我嘗試將Zuul
換成Spring Cloud Gateway
,由於Zuul
的阻塞IO
在網關層面實在太影響性能了。
前面已經說了,Spring Security
中的認證與受權方式是經過Servlet
技術套裝中的Filter
實現的,Tomcat
提供了Servlet
的運行環境。
Spring Cloud Netflix Zuul
集成Spring Boot Starter Web
也就是默認的Tomcat
實現網關,因此Zuul
中是有Servlet
的運行環境的。
而非阻塞的Spring Cloud Gateway
基於Spring WebFlux
框架,不支持Servlet
,底層採用高性能的Netty
服務器。
Spring WebFlux
與Spring MVC
的對比請看下圖:
因此當Spring Security
與Spring Cloud Gateway
集成時,就會出錯,Spring Security
須要的Servlet
環境沒有被知足。
這也是以前對Reactive
的理解不到位而引發的錯誤,WebFlux
環境下,應該集成Spring Security Reactive
,而非默認的Spring Security
。
經資料查閱,高性能的 Netty
十分受各大互聯網企業歡迎,Twitter
、Facebook
、蘋果、微博都在使用Netty
,具體Netty
爲何更適合高併發?之後咱們一塊兒學習。