引言: 本文系《認證鑒權與API權限控制在微服務架構中的設計與實現》系列的完結篇,前面三篇已經將認證鑒權與API權限控制的流程和主要細節講解完。本文比較長,對這個系列進行收尾,主要內容包括對授權和鑒權流程之外的endpoint以及Spring Security
過濾器部分踩坑的經歷。歡迎閱讀本系列文章。
1. 前文回顧
首先還是照例對前文進行回顧。在第一篇 認證鑒權與API權限控制在微服務架構中的設計與實現(一)介紹了該項目的背景以及技術調研與最后選型。第二篇認證鑒權與API權限控制在微服務架構中的設計與實現(二)畫出了簡要的登錄和校驗的流程圖,並重點講解了用戶身份的認證與token發放的具體實現。第三篇認證鑒權與API權限控制在微服務架構中的設計與實現(三)先介紹了資源服務器配置,以及其中涉及的配置類,后面重點講解了token以及API級別的鑒權。
本文將會講解剩余的兩個內置端點:注銷和刷新token。注銷token端點的處理與Spring Security
默認提供的有些’/logout’有些區別,不僅清空SpringSecurityContextHolder中的信息,還要增加對存儲token的清空。另一個刷新token端點其實和之前的請求授權是一樣的API,只是參數中的grant_type不一樣。
除了以上兩個內置端點,后面將會重點講下幾種Spring Security
過濾器。API級別的操作權限校驗本來設想是通過Spring Security
的過濾器實現,特地把這邊學習了一遍,踩了一遍坑。
最后是本系列的總結,並對於存在的不足和后續工作進行論述。
2. 其他端點
2.1 注銷端點
在第一篇中提到了Auth系統內置的注銷端點 /logout
,如果還記得第三篇資源服務器的配置,下面的關於/logout
配置一定不陌生。
1 //... 2 .and().logout() 3 .logoutUrl("/logout") 4 .clearAuthentication(true) 5 .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()) 6 .addLogoutHandler(customLogoutHandler());
上面配置的主要作用是:
- 設置注銷的URL
- 清空Authentication信息
- 設置注銷成功的處理方式
- 設置自定義的注銷處理方式
當然在LogoutConfigurer
中還有更多的設置選項,筆者此處列出項目所需要的配置項。這些配置項圍繞着LogoutFilter
過濾器。順帶講一下Spring Security
的過濾器。其使用了springSecurityFillterChian
作為了安全過濾的入口,各種過濾器按順序具體如下:
- SecurityContextPersistenceFilter:與SecurityContext安全上下文信息有關
- HeaderWriterFilter:給http響應添加一些Header
- CsrfFilter:防止csrf攻擊,默認開啟
- LogoutFilter:處理注銷的過濾器
- UsernamePasswordAuthenticationFilter:表單認證過濾器
- RequestCacheAwareFilter:緩存request請求
- SecurityContextHolderAwareRequestFilter:此過濾器對ServletRequest進行了一次包裝,使得request具有更加豐富的API
- AnonymousAuthenticationFilter:匿名身份過濾器
- SessionManagementFilter:session相關的過濾器,常用來防止session-fixation protection attack,以及限制同一用戶開啟多個會話的數量
- ExceptionTranslationFilter:異常處理過濾器
- FilterSecurityInterceptor:web應用安全的關鍵Filter
各種過濾器簡單標注了作用,在下一節重點講其中的幾個過濾器。注銷過濾器排在靠前的位置,我們一起看下LogoutFilter
的UML類圖。
類圖和我們之前配置時的思路是一致的,HttpSecurity
創建了LogoutConfigurer
,我們在這邊配置了LogoutConfigurer
的一些屬性。同時LogoutConfigurer
根據這些屬性創建了LogoutFilter
。
LogoutConfigurer
的配置,第一和第二點就不用再詳細解釋了,一個是設置端點,另一個是清空認證信息。
對於第三點,配置注銷成功的處理方式。由於項目是前后端分離,客戶端只需要知道執行成功該API接口的狀態,並不用返回具體的頁面或者繼續向下傳遞請求。因此,這邊配置了默認的HttpStatusReturningLogoutSuccessHandler
,成功直接返回狀態碼200。
對於第四點配置,自定義注銷處理的方法。這邊需要借助TokenStore
,對token進行操作。TokenStore
在之前文章的配置中已經講過,使用的是JdbcTokenStore。首先校驗請求的合法性,如果合法則對其進行操作,先后移除refreshToken
和existingAccessToken
。
1 public class CustomLogoutHandler implements LogoutHandler { 2 //... 3 @Override 4 public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { 5 //確定注入了tokenStore 6 Assert.notNull(tokenStore, "tokenStore must be set"); 7 //獲取頭部的認證信息 8 String token = request.getHeader("Authorization"); 9 Assert.hasText(token, "token must be set"); 10 //校驗token是否符合JwtBearer格式 11 if (isJwtBearerToken(token)) { 12 token = token.substring(6); 13 OAuth2AccessToken existingAccessToken = tokenStore.readAccessToken(token); 14 OAuth2RefreshToken refreshToken; 15 if (existingAccessToken != null) { 16 if (existingAccessToken.getRefreshToken() != null) { 17 LOGGER.info("remove refreshToken!", existingAccessToken.getRefreshToken()); 18 refreshToken = existingAccessToken.getRefreshToken(); 19 tokenStore.removeRefreshToken(refreshToken); 20 } 21 LOGGER.info("remove existingAccessToken!", existingAccessToken); 22 tokenStore.removeAccessToken(existingAccessToken); 23 } 24 return; 25 } else { 26 throw new BadClientCredentialsException(); 27 } 28 } 29 //... 30 }
執行如下請求:
method: get url: http://localhost:9000/logout header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ= }
注銷成功則會返回200,將token和SecurityContextHolder進行清空。
2.2 刷新端點
在第一篇就已經講過,由於token的時效一般不會很長,而refresh token一般周期會很長,為了不影響用戶的體驗,可以使用refresh token去動態的刷新token。刷新token主要與RefreshTokenGranter
有關,CompositeTokenGranter
管理一個List列表,每一種grantType對應一個具體的真正授權者,refresh_ token對應的granter就是RefreshTokenGranter
,而granter內部則是通過grantType來區分是否是各自的授權類型。執行如下請求:
method: post url: http://localhost:12000/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ= }
在refresh_ token正確的情況下,其返回的response和/oauth/token得到正常的響應是一樣的。具體的代碼可以參閱第二篇的講解。
3. Spring Security
過濾器
在上一節我們介紹了內置的兩個端點的實現細節,還提到了HttpSecurity
過濾器,因為注銷端點的實現就是通過過濾器的作用。核心的過濾器主要有:
- FilterSecurityInterceptor
- UsernamePasswordAuthenticationFilter
- SecurityContextPersistenceFilter
- ExceptionTranslationFilter
這一節將重點介紹其中的UsernamePasswordAuthenticationFilter
和FilterSecurityInterceptor
。
3.1 UsernamePasswordAuthenticationFilter
筆者在剛開始看關於過濾器的文章,對於UsernamePasswordAuthenticationFilter
有不少的文章介紹。如果只是引入Spring-Security,必然會與/login
端點熟悉。SpringSecurity強制要求我們的表單登錄頁面必須是以POST方式向/login URL提交請求,而且要求用戶名和密碼的參數名必須是username和password。如果不符合,則不能正常工作。原因在於,當我們調用了HttpSecurity對象的formLogin方法時,其最終會給我們注冊一個過濾器UsernamePasswordAuthenticationFilter
。看一下該過濾器的源碼。
1 public class UsernamePasswordAuthenticationFilter extends 2 AbstractAuthenticationProcessingFilter { 3 //用戶名、密碼 4 public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; 5 public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; 6 private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; 7 private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; 8 private boolean postOnly = true; 9 //post請求/login 10 public UsernamePasswordAuthenticationFilter() { 11 super(new AntPathRequestMatcher("/login", "POST")); 12 } 13 //實現抽象類AbstractAuthenticationProcessingFilter的抽象方法,嘗試驗證 14 public Authentication attemptAuthentication(HttpServletRequest request, 15 HttpServletResponse response) throws AuthenticationException { 16 if (postOnly && !request.getMethod().equals("POST")) { 17 throw new AuthenticationServiceException( 18 "Authentication method not supported: " + request.getMethod()); 19 } 20 String username = obtainUsername(request); 21 String password = obtainPassword(request); 22 23 //··· 24 username = username.trim(); 25 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( 26 username, password); 27 //··· 28 return this.getAuthenticationManager().authenticate(authRequest); 29 } 30 }
1 public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean 2 implements ApplicationEventPublisherAware, MessageSourceAware { 3 //... 4 5 //調用requiresAuthentication,判斷請求是否需要authentication,如果需要則調用attemptAuthentication 6 //有三種結果可能返回: 7 //1.Authentication對象 8 //2. AuthenticationException 9 //3. Authentication對象為空 10 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 11 throws IOException, ServletException { 12 HttpServletRequest request = (HttpServletRequest) req; 13 HttpServletResponse response = (HttpServletResponse) res; 14 //不需要校驗,繼續傳遞 15 if (!requiresAuthentication(request, response)) { 16 chain.doFilter(request, response); 17 return; 18 } 19 Authentication authResult; 20 try { 21 authResult = attemptAuthentication(request, response); 22 if (authResult == null) { 23 // return immediately as subclass has indicated that it hasn't completed authentication 24 return; 25 } 26 sessionStrategy.onAuthentication(authResult, request, response); 27 } 28 //... 29 catch (AuthenticationException failed) { 30 // Authentication failed 31 unsuccessfulAuthentication(request, response, failed); 32 return; 33 } 34 // Authentication success 35 if (continueChainBeforeSuccessfulAuthentication) { 36 chain.doFilter(request, response); 37 } 38 successfulAuthentication(request, response, chain, authResult); 39 } 40 //實際執行的authentication,繼承類必須實現該抽象方法 41 public abstract Authentication attemptAuthentication(HttpServletRequest request, 42 HttpServletResponse response) throws AuthenticationException, IOException, 43 ServletException; 44 //成功authentication的默認行為 45 protected void successfulAuthentication(HttpServletRequest request, 46 HttpServletResponse response, FilterChain chain, Authentication authResult) 47 throws IOException, ServletException { 48 //... 49 } 50 //失敗authentication的默認行為 51 protected void unsuccessfulAuthentication(HttpServletRequest request, 52 HttpServletResponse response, AuthenticationException failed) 53 throws IOException, ServletException { 54 //... 55 } 56 ... 57 //設置AuthenticationManager 58 public void setAuthenticationManager(AuthenticationManager authenticationManager) { 59 this.authenticationManager = authenticationManager; 60 } 61 ... 62 }
UsernamePasswordAuthenticationFilter
因為繼承了AbstractAuthenticationProcessingFilter
才擁有過濾器的功能。AbstractAuthenticationProcessingFilter
要求設置一個authenticationManager,authenticationManager的實現類將實際處理請求的認證。AbstractAuthenticationProcessingFilter
將攔截符合過濾規則的request,並試圖執行認證。子類必須實現 attemptAuthentication 方法,這個方法執行具體的認證。
認證之后的處理和上注銷的差不多。如果認證成功,將會把返回的Authentication對象存放在SecurityContext,並調用SuccessHandler,也可以設置指定的URL和指定自定義的處SuccessHandler。如果認證失敗,默認會返回401代碼給客戶端,也可以設置URL,指定自定義的處理FailureHandler。
基於UsernamePasswordAuthenticationFilter
自定義的AuthenticationFilte
還是挺多案例的,這邊推薦一篇博文Spring Security(五)–動手實現一個IP_Login,寫得比較詳細。
3.2 FilterSecurityInterceptor
FilterSecurityInterceptor
是filterchain中比較復雜,也是比較核心的過濾器,主要負責web應用安全授權的工作。首先看下對於自定義的FilterSecurityInterceptor
配置。
1 @Override 2 public void configure(HttpSecurity http) throws Exception { 3 4 ... 5 //添加CustomSecurityFilter,過濾器的順序放在FilterSecurityInterceptor 6 http.antMatcher("/oauth/check_token").addFilterAt(customSecurityFilter(), FilterSecurityInterceptor.class); 7 } 8 //提供實例化的自定義過濾器 9 @Bean 10 public CustomSecurityFilter customSecurityFilter() { 11 return new CustomSecurityFilter(); 12 }
從上述配置可以看到,在FilterSecurityInterceptor
的位置注冊了CustomSecurityFilter
,對於匹配到/oauth/check_token
,則會調用該進入該過濾器。下圖為FilterSecurityInterceptor
的類圖,在其中還添加了CustomSecurityFilter
和相關實現的接口的類,方便讀者對比着看。
CustomSecurityFilter
是模仿FilterSecurityInterceptor
實現,繼承AbstractSecurityInterceptor
和實現Filter
接口。整個過程需要依賴AuthenticationManager
、AccessDecisionManager
和FilterInvocationSecurityMetadataSource
。AuthenticationManager
是認證管理器,實現用戶認證的入口;AccessDecisionManager
是訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源;FilterInvocationSecurityMetadataSource
是資源源數據定義,即定義某一資源可以被哪些角色訪問。
從上面的類圖中可以看到自定義的CustomSecurityFilter
同時又實現了AccessDecisionManager
和FilterInvocationSecurityMetadataSource
。分別為SecureResourceFilterInvocationDefinitionSource
和SecurityAccessDecisionManager
。下面分析下主要的配置。
1 //通過一個實現的filter,對HTTP資源進行安全處理 2 public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { 3 //被filter chain真實調用的方法,通過invoke代理 4 public void doFilter(ServletRequest request, ServletResponse response, 5 FilterChain chain) throws IOException, ServletException { 6 FilterInvocation fi = new FilterInvocation(request, response, chain); 7 invoke(fi); 8 } 9 //代理的方法 10 public void invoke(FilterInvocation fi) throws IOException, ServletException { 11 //...省略 12 } 13 }
上述代碼是FilterSecurityInterceptor
中的實現,具體實現細節就沒列出了,我們這邊重點在於對自定義的實現進行講解。
1 public class CustomSecurityFilter extends AbstractSecurityInterceptor implements Filter { 2 3 @Autowired 4 SecureResourceFilterInvocationDefinitionSource invocationSource; 5 @Autowired 6 private AuthenticationManager authenticationManager; 7 @Autowired 8 private SecurityAccessDecisionManager decisionManager; 9 //設置父類中的屬性 10 @PostConstruct 11 public void init() { 12 super.setAccessDecisionManager(decisionManager); 13 super.setAuthenticationManager(authenticationManager); 14 } 15 //主要的過濾方法,與原來的一致 16 @Override 17 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 18 //logger.info("doFilter in Security "); 19 //構造一個FilterInvocation,封裝request, response, chain 20 FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain); 21 //beforeInvocation會調用SecureResourceDataSource中的邏輯,類似於aop中的before 22 InterceptorStatusToken token = super.beforeInvocation(fi); 23 try { 24 //執行下一個攔截器 25 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 26 } finally { 27 //完成后續工作,類似於aop中的after 28 super.afterInvocation(token, null); 29 } 30 } 31 32 //... 33 34 //資源源數據定義,設置為自定義的SecureResourceFilterInvocationDefinitionSource 35 @Override 36 public SecurityMetadataSource obtainSecurityMetadataSource() { 37 return invocationSource; 38 } 39 }
上面自定義的CustomSecurityFilter
,與我們之前的講解是一樣的流程。主要依賴的三個接口都有在實現中實例化注入。看下父類的beforeInvocation方法,其中省略了一些不重要的代碼片段。
1 protected InterceptorStatusToken beforeInvocation(Object object) { 2 //根據SecurityMetadataSource獲取配置的權限屬性 3 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); 4 //... 5 //判斷是否需要對認證實體重新認證,默認為否 6 Authentication authenticated = authenticateIfRequired(); 7 8 // Attempt authorization 9 try { 10 //決策管理器開始決定是否授權,如果授權失敗,直接拋出AccessDeniedException 11 this.accessDecisionManager.decide(authenticated, object, attributes); 12 } 13 catch (AccessDeniedException accessDeniedException) { 14 publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, 15 accessDeniedException)); 16 17 throw accessDeniedException; 18 } 19 }
上面代碼可以看出,第一步是根據SecurityMetadataSource獲取配置的權限屬性,accessDecisionManager會用到權限列表信息。然后判斷是否需要對認證實體重新認證,默認為否。第二步是接着決策管理器開始決定是否授權,如果授權失敗,直接拋出AccessDeniedException。
(1). 獲取配置的權限屬性
1 public class SecureResourceFilterInvocationDefinitionSource implements FilterInvocationSecurityMetadataSource, InitializingBean { 2 private PathMatcher matcher; 3 //map保存配置的URL對應的權限集 4 private static Map<String, Collection<ConfigAttribute>> map = new HashMap<>(); 5 //根據傳入的對象URL進行循環 6 @Override 7 public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { 8 logger.info("getAttributes"); 9 //應該做instanceof 10 FilterInvocation filterInvocation = (FilterInvocation) o; 11 //String method = filterInvocation.getHttpRequest().getMethod(); 12 String requestURI = filterInvocation.getRequestUrl(); 13 //循環資源路徑,當訪問的Url和資源路徑url匹配時,返回該Url所需要的權限 14 for (Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iterator = map.entrySet().iterator(); iter.hasNext(); ) { 15 Map.Entry<String, Collection<ConfigAttribute>> entry = iterator.next(); 16 String url = entry.getKey(); 17 if (matcher.match(url, requestURI)) { 18 return map.get(requestURI); 19 } 20 } 21 return null; 22 } 23 24 //... 25 26 //設置權限集,即上述的map 27 @Override 28 public void afterPropertiesSet() throws Exception { 29 logger.info("afterPropertiesSet"); 30 //用來匹配訪問資源路徑 31 this.matcher = new AntPathMatcher(); 32 //可以有多個權限 33 Collection<ConfigAttribute> atts = new ArrayList<>(); 34 ConfigAttribute c1 = new SecurityConfig("ROLE_ADMIN"); 35 atts.add(c1); 36 map.put("/oauth/check_token", atts); 37 } 38 }
上面是getAttributes()實現的具體細節,將請求的URL取出進行匹配事先設定的受限資源,最后返回需要的權限、角色。系統在啟動的時候就會讀取到配置的map集合,對於攔截到請求進行匹配。代碼中注釋比較詳細,這邊不多說。
(2). 決策管理器
1 public class SecurityAccessDecisionManager implements AccessDecisionManager { 2 //... 3 4 @Override 5 public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { 6 logger.info("decide url and permission"); 7 //集合為空 8 if (collection == null) { 9 return; 10 } 11 Iterator<ConfigAttribute> ite = collection.iterator(); 12 //判斷用戶所擁有的權限,是否符合對應的Url權限,如果實現了UserDetailsService,則用戶權限是loadUserByUsername返回用戶所對應的權限 13 while (ite.hasNext()) { 14 ConfigAttribute ca = ite.next(); 15 String needRole = ca.getAttribute(); 16 for (GrantedAuthority ga : authentication.getAuthorities()) { 17 logger.info("GrantedAuthority: {}", ga); 18 if (needRole.equals(ga.getAuthority())) { 19 return; 20 } 21 } 22 } 23 logger.error("AccessDecisionManager: no right!"); 24 throw new AccessDeniedException("no right!"); 25 } 26 27 //... 28 }
上面的代碼是決策管理器的實現,其邏輯也比較簡單,將請求所具有的權限與設定的受限資源所需的進行匹配,如果具有則返回,否則拋出沒有正確的權限異常。默認提供的決策管理器有三種,分別為AffirmativeBased、ConsensusBased、UnanimousBased,篇幅有限,我們這邊不再擴展了。
補充一下,所具有的權限是通過之前配置的認證方式,有password認證和client認證兩種。我們之前在授權服務器中配置了withClientDetails
,所以用frontend身份驗證獲得的權限是我們預先配置在數據庫中的authorities。
4. 總結
Auth系統主要功能是授權認證和鑒權。項目微服務化后,原有的單體應用基於HttpSession認證鑒權不能滿足微服務架構下的需求。每個微服務都需要對訪問進行鑒權,每個微應用都需要明確當前訪問用戶以及其權限,尤其當有多個客戶端,包括web端、移動端等等,單體應用架構下的鑒權方式就不是特別合適了。權限服務作為基礎的公共服務,也需要微服務化。
筆者的設計中,Auth服務一方面進行授權認證,另一方面是基於token進行身份合法性和API級別的權限校驗。對於某個服務的請求,經過網關會調用Auth服務,對token合法性進行驗證。同時筆者根據當前項目的整體情況,存在部分遺留服務,這些遺留服務又沒有足夠的時間和人力立馬進行微服務改造,而且還需要繼續運行。為了適配當前新的架構,采取的方案就是對這些遺留服務的操作API,在Auth服務進行API級別的操作權限鑒定。API級別的操作權限校驗需要的上下文信息需要結合業務,與客戶端進行商定,應該在token能取到相應信息,傳遞給Auth服務,不過應盡量減少在header取上下文校驗的信息。
筆者將本次開發Auth系統所涉及的大部分代碼及源碼進行了解析,至於沒有講到的一些內容和細節,讀者可以自行擴展。
5. 不足與后續工作
5.1 存在的不足
-
API級別操作權限校驗的通用性
(1). 對於API級別操作權限校驗,需要在網關處調用時構造相應的上下文信息。上下文信息基本依賴於 token中的payload,如果信息太多引起token太長,導致每次客戶端的請求頭部長度變長。
(2). 並不是所有的操作接口都能覆蓋到,這個問題是比較嚴重的,根據上下文集合很可能出現好多接口 的權限沒法鑒定,最后的結果就是API級別操作權限校驗失敗的是絕對沒有權限訪問該接口,而通過不一定能訪問,因為該接口涉及到的上下文根本沒法完全得到。我們的項目在現階段,定義的最小上下文集合能勉強覆蓋到,但是對於后面擴增的服務接口真的是不樂觀。
(3). 每個服務的每個接口都在Auth服務注冊其所需要的權限,太過麻煩,Auth服務需要額外維護這樣的信息。
-
網關處調用Auth服務帶來的系統吞吐量瓶頸
(1). 這個其實很容易理解,Auth服務作為公共的基礎服務,大多數服務接口都會需要鑒權,Auth服務需要經過復雜。
(2). 網關調用Auth服務,阻塞調用,只有等Auth服務返回校驗結果,才會做進一步處理。雖說Auth服務可以多實例部署,但是並發量大了之后,其瓶頸明顯可見,嚴重可能會造成整個系統的不可用。
5.2 后續工作
- 從整個系統設計角度來講,API級別操作權限后期將會分散在各個服務的接口上,由各個接口負責其所需要的權限、身份等。Spring Security對於接口級別的權限校驗也是支持的,之所以采用這樣的做法,也是為了兼容新服務和遺留的服務,主要是針對遺留服務,新的服務采用的是分散在各個接口之上。
- 將API級別操作權限分散到各個服務接口之后,相應的能提升Auth服務的響應。網關能夠及時的對請求進行轉發或者拒絕。
- API級別操作權限所需要的上下文信息對各個接口真的設計的很復雜,這邊我們確實花了時間,同時管理移動服務的好幾百操作接口所對應的權限,非常煩。!
本文的源碼地址:
GitHub:https://github.com/keets2012/Auth-service
碼雲: https://gitee.com/keets/Auth-Service
參考
來源:http://blueskykong.com/2017/10/26/security4/