1. 程式人生 > >認證鑑權與API許可權控制在微服務架構中的設計與實現(四)

認證鑑權與API許可權控制在微服務架構中的設計與實現(四)

引言: 本文系《認證鑑權與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

3

4

5

6

 

//...

.and().logout()

.logoutUrl("/logout")

.clearAuthentication(true)

.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())

.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類圖。

logoutFilter

logoutFilter類圖

類圖和我們之前配置時的思路是一致的,HttpSecurity建立了LogoutConfigurer,我們在這邊配置了LogoutConfigurer的一些屬性。同時LogoutConfigurer根據這些屬性建立了LogoutFilter

LogoutConfigurer的配置,第一和第二點就不用再詳細解釋了,一個是設定端點,另一個是清空認證資訊。
對於第三點,配置登出成功的處理方式。由於專案是前後端分離,客戶端只需要知道執行成功該API介面的狀態,並不用返回具體的頁面或者繼續向下傳遞請求。因此,這邊配置了預設的HttpStatusReturningLogoutSuccessHandler,成功直接返回狀態碼200。
對於第四點配置,自定義登出處理的方法。這邊需要藉助TokenStore,對token進行操作。TokenStore在之前文章的配置中已經講過,使用的是JdbcTokenStore。首先校驗請求的合法性,如果合法則對其進行操作,先後移除refreshTokenexistingAccessToken

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

 

public class CustomLogoutHandler implements LogoutHandler {

//...

@Override

public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

//確定注入了tokenStore

Assert.notNull(tokenStore, "tokenStore must be set");

//獲取頭部的認證資訊

String token = request.getHeader("Authorization");

Assert.hasText(token, "token must be set");

//校驗token是否符合JwtBearer格式

if (isJwtBearerToken(token)) {

token = token.substring(6);

OAuth2AccessToken existingAccessToken = tokenStore.readAccessToken(token);

OAuth2RefreshToken refreshToken;

if (existingAccessToken != null) {

if (existingAccessToken.getRefreshToken() != null) {

LOGGER.info("remove refreshToken!", existingAccessToken.getRefreshToken());

refreshToken = existingAccessToken.getRefreshToken();

tokenStore.removeRefreshToken(refreshToken);

}

LOGGER.info("remove existingAccessToken!", existingAccessToken);

tokenStore.removeAccessToken(existingAccessToken);

}

return;

} else {

throw new BadClientCredentialsException();

}

}

//...

}

執行如下請求:

 

1

2

3

4

5

6

 

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來區分是否是各自的授權型別。執行如下請求:

 

1

2

3

4

5

6

 

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

這一節將重點介紹其中的UsernamePasswordAuthenticationFilterFilterSecurityInterceptor

3.1 UsernamePasswordAuthenticationFilter

筆者在剛開始看關於過濾器的文章,對於UsernamePasswordAuthenticationFilter有不少的文章介紹。如果只是引入Spring-Security,必然會與/login端點熟悉。SpringSecurity強制要求我們的表單登入頁面必須是以POST方式向/login URL提交請求,而且要求使用者名稱和密碼的引數名必須是username和password。如果不符合,則不能正常工作。原因在於,當我們呼叫了HttpSecurity物件的formLogin方法時,其最終會給我們註冊一個過濾器UsernamePasswordAuthenticationFilter。看一下該過濾器的原始碼。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

 

public class UsernamePasswordAuthenticationFilter extends

AbstractAuthenticationProcessingFilter {

//使用者名稱、密碼

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

private boolean postOnly = true;

//post請求/login

public UsernamePasswordAuthenticationFilter() {

super(new AntPathRequestMatcher("/login", "POST"));

}

//實現抽象類AbstractAuthenticationProcessingFilter的抽象方法,嘗試驗證

public Authentication attemptAuthentication(HttpServletRequest request,

HttpServletResponse response) throws AuthenticationException {

if (postOnly && !request.getMethod().equals("POST")) {

throw new AuthenticationServiceException(

"Authentication method not supported: " + request.getMethod());

}

String username = obtainUsername(request);

String password = obtainPassword(request);

//···

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(

username, password);

//···

return this.getAuthenticationManager().authenticate(authRequest);

}

}

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

 

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean

implements ApplicationEventPublisherAware, MessageSourceAware {

//...

//呼叫requiresAuthentication,判斷請求是否需要authentication,如果需要則呼叫attemptAuthentication

//有三種結果可能返回:

//1.Authentication物件

//2. AuthenticationException

//3. Authentication物件為空

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 immediately as subclass has indicated that it hasn't completed authentication

return;

}

sessionStrategy.onAuthentication(authResult, request, response);

}

//...

catch (AuthenticationException failed) {

// Authentication failed

unsuccessfulAuthentication(request, response, failed);

return;

}

// Authentication success

if (continueChainBeforeSuccessfulAuthentication) {

chain.doFilter(request, response);

}

successfulAuthentication(request, response, chain, authResult);

}

//實際執行的authentication,繼承類必須實現該抽象方法

public abstract Authentication attemptAuthentication(HttpServletRequest request,

HttpServletResponse response) throws AuthenticationException, IOException,

ServletException;

//成功authentication的預設行為

protected void successfulAuthentication(HttpServletRequest request,

HttpServletResponse response, FilterChain chain, Authentication authResult)

throws IOException, ServletException {

//...

}

//失敗authentication的預設行為

protected void unsuccessfulAuthentication(HttpServletRequest request,

HttpServletResponse response, AuthenticationException failed)

throws IOException, ServletException {

//...

}

...

//設定AuthenticationManager

public void setAuthenticationManager(AuthenticationManager authenticationManager) {

this.authenticationManager = authenticationManager;

}

...

}

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

2

3

4

5

6

7

8

9

10

11

12

13

 

@Override

public void configure(HttpSecurity http) throws Exception {

...

//新增CustomSecurityFilter,過濾器的順序放在FilterSecurityInterceptor

http.antMatcher("/oauth/check_token").addFilterAt(customSecurityFilter(), FilterSecurityInterceptor.class);

}

//提供例項化的自定義過濾器

@Bean

public CustomSecurityFilter customSecurityFilter() {

return new CustomSecurityFilter();

}

從上述配置可以看到,在FilterSecurityInterceptor的位置註冊了CustomSecurityFilter,對於匹配到/oauth/check_token,則會呼叫該進入該過濾器。下圖為FilterSecurityInterceptor的類圖,在其中還添加了CustomSecurityFilter和相關實現的介面的類,方便讀者對比著看。

FilterSecurityInterceptor

FilterSecurityInterceptor類圖

CustomSecurityFilter是模仿FilterSecurityInterceptor實現,繼承AbstractSecurityInterceptor和實現Filter介面。整個過程需要依賴AuthenticationManagerAccessDecisionManagerFilterInvocationSecurityMetadataSource
AuthenticationManager是認證管理器,實現使用者認證的入口;AccessDecisionManager是訪問決策器,決定某個使用者具有的角色,是否有足夠的許可權去訪問某個資源;FilterInvocationSecurityMetadataSource是資源源資料定義,即定義某一資源可以被哪些角色訪問。
從上面的類圖中可以看到自定義的CustomSecurityFilter同時又實現了
AccessDecisionManagerFilterInvocationSecurityMetadataSource。分別為SecureResourceFilterInvocationDefinitionSourceSecurityAccessDecisionManager。下面分析下主要的配置。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

 

//通過一個實現的filter,對HTTP資源進行安全處理

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

//被filter chain真實呼叫的方法,通過invoke代理

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

FilterInvocation fi = new FilterInvocation(request, response, chain);

invoke(fi);

}

//代理的方法

public void invoke(FilterInvocation fi) throws IOException, ServletException {

//...省略

}

}

上述程式碼是FilterSecurityInterceptor中的實現,具體實現細節就沒列出了,我們這邊重點在於對自定義的實現進行講解。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

 

public class CustomSecurityFilter extends AbstractSecurityInterceptor implements Filter {

@Autowired

SecureResourceFilterInvocationDefinitionSource invocationSource;

@Autowired

private AuthenticationManager authenticationManager;

@Autowired

private SecurityAccessDecisionManager decisionManager;

//設定父類中的屬性

@PostConstruct

public void init() {

super.setAccessDecisionManager(decisionManager);

super.setAuthenticationManager(authenticationManager);

}

//主要的過濾方法,與原來的一致

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

//logger.info("doFilter in Security ");

//構造一個FilterInvocation,封裝request, response, chain

FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);

//beforeInvocation會呼叫SecureResourceDataSource中的邏輯,類似於aop中的before

InterceptorStatusToken token = super.beforeInvocation(fi);

try {

//執行下一個攔截器

fi.getChain().doFilter(fi.getRequest(), fi.getResponse());

} finally {

//完成後續工作,類似於aop中的after

super.afterInvocation(token, null);

}

}

//...

//資源源資料定義,設定為自定義的SecureResourceFilterInvocationDefinitionSource

@Override

public SecurityMetadataSource obtainSecurityMetadataSource() {

return invocationSource;

}

}

上面自定義的CustomSecurityFilter,與我們之前的講解是一樣的流程。主要依賴的三個介面都有在實現中例項化注入。看下父類的beforeInvocation方法,其中省略了一些不重要的程式碼片段。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

 

protected InterceptorStatusToken beforeInvocation(Object object) {

//根據SecurityMetadataSource獲取配置的許可權屬性

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

//...

//判斷是否需要對認證實體重新認證,預設為否

Authentication authenticated = authenticateIfRequired();

// Attempt authorization

try {

//決策管理器開始決定是否授權,如果授權失敗,直接丟擲AccessDeniedException

this.accessDecisionManager.decide(authenticated, object, attributes);

}

catch (AccessDeniedException accessDeniedException) {

publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,

accessDeniedException));

throw accessDeniedException;

}

}

上面程式碼可以看出,第一步是根據SecurityMetadataSource獲取配置的許可權屬性,accessDecisionManager會用到許可權列表資訊。然後判斷是否需要對認證實體重新認證,預設為否。第二步是接著決策管理器開始決定是否授權,如果授權失敗,直接丟擲AccessDeniedException。

(1). 獲取配置的許可權屬性

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

 

public class SecureResourceFilterInvocationDefinitionSource implements FilterInvocationSecurityMetadataSource, InitializingBean {

private PathMatcher matcher;

//map儲存配置的URL對應的許可權集

private static Map<String, Collection<ConfigAttribute>> map = new HashMap<>();

//根據傳入的物件URL進行迴圈

@Override

public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {

logger.info("getAttributes");

//應該做instanceof

FilterInvocation filterInvocation = (FilterInvocation) o;

//String method = filterInvocation.getHttpRequest().getMethod();

String requestURI = filterInvocation.getRequestUrl();

//迴圈資源路徑,當訪問的Url和資源路徑url匹配時,返回該Url所需要的許可權

for (Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iterator = map.entrySet().iterator(); iter.hasNext(); ) {

Map.Entry<String, Collection<ConfigAttribute>> entry = iterator.next();

String url = entry.getKey();

if (matcher.match(url, requestURI)) {

return map.get(requestURI);

}

}

return null;

}

//...

//設定許可權集,即上述的map

@Override

public void afterPropertiesSet() throws Exception {

logger.info("afterPropertiesSet");

//用來匹配訪問資源路徑

this.matcher = new AntPathMatcher();

//可以有多個許可權

Collection<ConfigAttribute> atts = new ArrayList<>();

ConfigAttribute c1 = new SecurityConfig("ROLE_ADMIN");

atts.add(c1);

map.put("/oauth/check_token", atts);

}

}

上面是getAttributes()實現的具體細節,將請求的URL取出進行匹配事先設定的受限資源,最後返回需要的許可權、角色。系統在啟動的時候就會讀取到配置的map集合,對於攔截到請求進行匹配。程式碼中註釋比較詳細,這邊不多說。

(2). 決策管理器

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

 

public class SecurityAccessDecisionManager implements AccessDecisionManager {

//...

@Override

public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

logger.info("decide url and permission");

//集合為空

if (collection == null) {

return;

}

Iterator<ConfigAttribute> ite = collection.iterator();

//判斷使用者所擁有的許可權,是否符合對應的Url許可權,如果實現了UserDetailsService,則使用者許可權是loadUserByUsername返回使用者所對應的許可權

while (ite.hasNext()) {

ConfigAttribute ca = ite.next();

String needRole = ca.getAttribute();

for (GrantedAuthority ga : authentication.getAuthorities()) {

logger.info("GrantedAuthority: {}", ga);

if (needRole.equals(ga.getAuthority())) {

return;

}

}

}

logger.error("AccessDecisionManager: no right!");

throw new AccessDeniedException("no right!");

}

//...

}

上面的程式碼是決策管理器的實現,其邏輯也比較簡單,將請求所具有的許可權與設定的受限資源所需的進行匹配,如果具有則返回,否則丟擲沒有正確的許可權異常。預設提供的決策管理器有三種,分別為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


參考

  1. 配置表單登入
  2. Spring Security3原始碼分析-FilterSecurityInterceptor分析
  3. Core Security Filters
  4. Spring Security(四)–核心過濾器原始碼分析