引言: 本文系《認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)》系列的完結(jié)篇盆顾,前面三篇已經(jīng)將認(rèn)證鑒權(quán)與API權(quán)限控制的流程和主要細(xì)節(jié)講解完孩饼。本文比較長消请,對這個(gè)系列進(jìn)行收尾汹桦,主要內(nèi)容包括對授權(quán)和鑒權(quán)流程之外的endpoint以及Spring Security
過濾器部分踩坑的經(jīng)歷朽合。歡迎閱讀本系列文章。
1. 前文回顧
首先還是照例對前文進(jìn)行回顧扔水。在第一篇 認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(一)介紹了該項(xiàng)目的背景以及技術(shù)調(diào)研與最后選型痛侍。第二篇認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(二)畫出了簡要的登錄和校驗(yàn)的流程圖,并重點(diǎn)講解了用戶身份的認(rèn)證與token發(fā)放的具體實(shí)現(xiàn)魔市。第三篇認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(三)先介紹了資源服務(wù)器配置主届,以及其中涉及的配置類,后面重點(diǎn)講解了token以及API級(jí)別的鑒權(quán)待德。
本文將會(huì)講解剩余的兩個(gè)內(nèi)置端點(diǎn):注銷和刷新token君丁。注銷token端點(diǎn)的處理與Spring Security
默認(rèn)提供的有些'/logout'有些區(qū)別,不僅清空SpringSecurityContextHolder中的信息将宪,還要增加對存儲(chǔ)token的清空绘闷。另一個(gè)刷新token端點(diǎn)其實(shí)和之前的請求授權(quán)是一樣的API橡庞,只是參數(shù)中的grant_type不一樣。
除了以上兩個(gè)內(nèi)置端點(diǎn)簸喂,后面將會(huì)重點(diǎn)講下幾種Spring Security
過濾器毙死。API級(jí)別的操作權(quán)限校驗(yàn)本來設(shè)想是通過Spring Security
的過濾器實(shí)現(xiàn)燎潮,特地把這邊學(xué)習(xí)了一遍喻鳄,踩了一遍坑。
最后是本系列的總結(jié)确封,并對于存在的不足和后續(xù)工作進(jìn)行論述除呵。
2. 其他端點(diǎn)
2.1 注銷端點(diǎn)
在第一篇中提到了Auth系統(tǒng)內(nèi)置的注銷端點(diǎn) /logout
,如果還記得第三篇資源服務(wù)器的配置爪喘,下面的關(guān)于/logout
配置一定不陌生颜曾。
//...
.and().logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.addLogoutHandler(customLogoutHandler());
上面配置的主要作用是:
- 設(shè)置注銷的URL
- 清空Authentication信息
- 設(shè)置注銷成功的處理方式
- 設(shè)置自定義的注銷處理方式
當(dāng)然在LogoutConfigurer
中還有更多的設(shè)置選項(xiàng),筆者此處列出項(xiàng)目所需要的配置項(xiàng)秉剑。這些配置項(xiàng)圍繞著LogoutFilter
過濾器泛豪。順帶講一下Spring Security
的過濾器。其使用了springSecurityFillterChian
作為了安全過濾的入口侦鹏,各種過濾器按順序具體如下:
- SecurityContextPersistenceFilter:與SecurityContext安全上下文信息有關(guān)
- HeaderWriterFilter:給http響應(yīng)添加一些Header
- CsrfFilter:防止csrf攻擊诡曙,默認(rèn)開啟
- LogoutFilter:處理注銷的過濾器
- UsernamePasswordAuthenticationFilter:表單認(rèn)證過濾器
- RequestCacheAwareFilter:緩存request請求
- SecurityContextHolderAwareRequestFilter:此過濾器對ServletRequest進(jìn)行了一次包裝,使得request具有更加豐富的API
- AnonymousAuthenticationFilter:匿名身份過濾器
- SessionManagementFilter:session相關(guān)的過濾器略水,常用來防止session-fixation protection attack价卤,以及限制同一用戶開啟多個(gè)會(huì)話的數(shù)量
- ExceptionTranslationFilter:異常處理過濾器
- FilterSecurityInterceptor:web應(yīng)用安全的關(guān)鍵Filter
各種過濾器簡單標(biāo)注了作用,在下一節(jié)重點(diǎn)講其中的幾個(gè)過濾器渊涝。注銷過濾器排在靠前的位置慎璧,我們一起看下LogoutFilter
的UML類圖。
類圖和我們之前配置時(shí)的思路是一致的跨释,HttpSecurity
創(chuàng)建了LogoutConfigurer
胸私,我們在這邊配置了LogoutConfigurer
的一些屬性。同時(shí)LogoutConfigurer
根據(jù)這些屬性創(chuàng)建了LogoutFilter
鳖谈。
LogoutConfigurer
的配置盖文,第一和第二點(diǎn)就不用再詳細(xì)解釋了,一個(gè)是設(shè)置端點(diǎn)蚯姆,另一個(gè)是清空認(rèn)證信息五续。
對于第三點(diǎn),配置注銷成功的處理方式龄恋。由于項(xiàng)目是前后端分離疙驾,客戶端只需要知道執(zhí)行成功該API接口的狀態(tài),并不用返回具體的頁面或者繼續(xù)向下傳遞請求郭毕。因此它碎,這邊配置了默認(rèn)的HttpStatusReturningLogoutSuccessHandler
,成功直接返回狀態(tài)碼200。
對于第四點(diǎn)配置扳肛,自定義注銷處理的方法傻挂。這邊需要借助TokenStore
,對token進(jìn)行操作挖息。TokenStore
在之前文章的配置中已經(jīng)講過金拒,使用的是JdbcTokenStore。首先校驗(yàn)請求的合法性套腹,如果合法則對其進(jìn)行操作绪抛,先后移除refreshToken
和existingAccessToken
。
public class CustomLogoutHandler implements LogoutHandler {
//...
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//確定注入了tokenStore
Assert.notNull(tokenStore, "tokenStore must be set");
//獲取頭部的認(rèn)證信息
String token = request.getHeader("Authorization");
Assert.hasText(token, "token must be set");
//校驗(yàn)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();
}
}
//...
}
執(zhí)行如下請求:
method: get
url: http://localhost:9000/logout
header:
{
Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}
注銷成功則會(huì)返回200电禀,將token和SecurityContextHolder進(jìn)行清空幢码。
2.2 刷新端點(diǎn)
在第一篇就已經(jīng)講過,由于token的時(shí)效一般不會(huì)很長尖飞,而refresh_ token一般周期會(huì)很長症副,為了不影響用戶的體驗(yàn),可以使用refresh_ token去動(dòng)態(tài)的刷新token政基。刷新token主要與RefreshTokenGranter
有關(guān)贞铣,CompositeTokenGranter
管理一個(gè)List列表,每一種grantType對應(yīng)一個(gè)具體的真正授權(quán)者腋么,refresh_ token對應(yīng)的granter就是RefreshTokenGranter
咕娄,而granter內(nèi)部則是通過grantType來區(qū)分是否是各自的授權(quán)類型。執(zhí)行如下請求:
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得到正常的響應(yīng)是一樣的圣勒。具體的代碼可以參閱第二篇的講解。
3. Spring Security
過濾器
在上一節(jié)我們介紹了內(nèi)置的兩個(gè)端點(diǎn)的實(shí)現(xiàn)細(xì)節(jié)摧扇,還提到了HttpSecurity
過濾器圣贸,因?yàn)樽N端點(diǎn)的實(shí)現(xiàn)就是通過過濾器的作用。核心的過濾器主要有:
- FilterSecurityInterceptor
- UsernamePasswordAuthenticationFilter
- SecurityContextPersistenceFilter
- ExceptionTranslationFilter
這一節(jié)將重點(diǎn)介紹其中的UsernamePasswordAuthenticationFilter
和FilterSecurityInterceptor
扛稽。
3.1 UsernamePasswordAuthenticationFilter
筆者在剛開始看關(guān)于過濾器的文章吁峻,對于UsernamePasswordAuthenticationFilter
有不少的文章介紹。如果只是引入Spring-Security在张,必然會(huì)與/login
端點(diǎn)熟悉用含。SpringSecurity強(qiáng)制要求我們的表單登錄頁面必須是以POST方式向/login URL提交請求,而且要求用戶名和密碼的參數(shù)名必須是username和password帮匾。如果不符合啄骇,則不能正常工作。原因在于瘟斜,當(dāng)我們調(diào)用了HttpSecurity對象的formLogin方法時(shí)缸夹,其最終會(huì)給我們注冊一個(gè)過濾器UsernamePasswordAuthenticationFilter
痪寻。看一下該過濾器的源碼虽惭。
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"));
}
//實(shí)現(xiàn)抽象類AbstractAuthenticationProcessingFilter的抽象方法,嘗試驗(yàn)證
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);
}
}
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
//...
//調(diào)用requiresAuthentication芽唇,判斷請求是否需要authentication顾画,如果需要?jiǎng)t調(diào)用attemptAuthentication
//有三種結(jié)果可能返回:
//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;
//不需要校驗(yàn),繼續(xù)傳遞
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);
}
//實(shí)際執(zhí)行的authentication披摄,繼承類必須實(shí)現(xiàn)該抽象方法
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
//成功authentication的默認(rèn)行為
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//...
}
//失敗authentication的默認(rèn)行為
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
//...
}
...
//設(shè)置AuthenticationManager
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
...
}
UsernamePasswordAuthenticationFilter
因?yàn)槔^承了AbstractAuthenticationProcessingFilter
才擁有過濾器的功能亲雪。AbstractAuthenticationProcessingFilter
要求設(shè)置一個(gè)authenticationManager勇凭,authenticationManager的實(shí)現(xiàn)類將實(shí)際處理請求的認(rèn)證疚膊。AbstractAuthenticationProcessingFilter
將攔截符合過濾規(guī)則的request,并試圖執(zhí)行認(rèn)證虾标。子類必須實(shí)現(xiàn) attemptAuthentication 方法寓盗,這個(gè)方法執(zhí)行具體的認(rèn)證。
認(rèn)證之后的處理和上注銷的差不多璧函。如果認(rèn)證成功傀蚌,將會(huì)把返回的Authentication對象存放在SecurityContext,并調(diào)用SuccessHandler蘸吓,也可以設(shè)置指定的URL和指定自定義的處SuccessHandler善炫。如果認(rèn)證失敗,默認(rèn)會(huì)返回401代碼給客戶端库继,也可以設(shè)置URL箩艺,指定自定義的處理FailureHandler。
基于UsernamePasswordAuthenticationFilter
自定義的AuthenticationFilte
還是挺多案例的宪萄,這邊推薦一篇博文Spring Security(五)--動(dòng)手實(shí)現(xiàn)一個(gè)IP_Login艺谆,寫得比較詳細(xì)。
3.2 FilterSecurityInterceptor
FilterSecurityInterceptor
是filterchain中比較復(fù)雜拜英,也是比較核心的過濾器静汤,主要負(fù)責(zé)web應(yīng)用安全授權(quán)的工作。首先看下對于自定義的FilterSecurityInterceptor
配置居凶。
@Override
public void configure(HttpSecurity http) throws Exception {
...
//添加CustomSecurityFilter虫给,過濾器的順序放在FilterSecurityInterceptor
http.antMatcher("/oauth/check_token").addFilterAt(customSecurityFilter(), FilterSecurityInterceptor.class);
}
//提供實(shí)例化的自定義過濾器
@Bean
public CustomSecurityFilter customSecurityFilter() {
return new CustomSecurityFilter();
}
從上述配置可以看到,在FilterSecurityInterceptor
的位置注冊了CustomSecurityFilter
侠碧,對于匹配到/oauth/check_token
抹估,則會(huì)調(diào)用該進(jìn)入該過濾器。下圖為FilterSecurityInterceptor
的類圖舆床,在其中還添加了CustomSecurityFilter
和相關(guān)實(shí)現(xiàn)的接口的類棋蚌,方便讀者對比著看嫁佳。
CustomSecurityFilter
是模仿FilterSecurityInterceptor
實(shí)現(xiàn),繼承AbstractSecurityInterceptor
和實(shí)現(xiàn)Filter
接口谷暮。整個(gè)過程需要依賴AuthenticationManager
蒿往、AccessDecisionManager
和FilterInvocationSecurityMetadataSource
。
AuthenticationManager
是認(rèn)證管理器湿弦,實(shí)現(xiàn)用戶認(rèn)證的入口瓤漏;AccessDecisionManager
是訪問決策器,決定某個(gè)用戶具有的角色颊埃,是否有足夠的權(quán)限去訪問某個(gè)資源蔬充;FilterInvocationSecurityMetadataSource
是資源源數(shù)據(jù)定義,即定義某一資源可以被哪些角色訪問班利。
從上面的類圖中可以看到自定義的CustomSecurityFilter
同時(shí)又實(shí)現(xiàn)了
AccessDecisionManager
和FilterInvocationSecurityMetadataSource
饥漫。分別為SecureResourceFilterInvocationDefinitionSource
和SecurityAccessDecisionManager
。下面分析下主要的配置罗标。
//通過一個(gè)實(shí)現(xiàn)的filter庸队,對HTTP資源進(jìn)行安全處理
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//被filter chain真實(shí)調(diào)用的方法,通過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
中的實(shí)現(xiàn)闯割,具體實(shí)現(xiàn)細(xì)節(jié)就沒列出了彻消,我們這邊重點(diǎn)在于對自定義的實(shí)現(xiàn)進(jìn)行講解。
public class CustomSecurityFilter extends AbstractSecurityInterceptor implements Filter {
@Autowired
SecureResourceFilterInvocationDefinitionSource invocationSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecurityAccessDecisionManager decisionManager;
//設(shè)置父類中的屬性
@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 ");
//構(gòu)造一個(gè)FilterInvocation宾尚,封裝request, response, chain
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
//beforeInvocation會(huì)調(diào)用SecureResourceDataSource中的邏輯,類似于aop中的before
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//執(zhí)行下一個(gè)攔截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
//完成后續(xù)工作谢澈,類似于aop中的after
super.afterInvocation(token, null);
}
}
//...
//資源源數(shù)據(jù)定義煌贴,設(shè)置為自定義的SecureResourceFilterInvocationDefinitionSource
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return invocationSource;
}
}
上面自定義的CustomSecurityFilter
,與我們之前的講解是一樣的流程澳化。主要依賴的三個(gè)接口都有在實(shí)現(xiàn)中實(shí)例化注入崔步。看下父類的beforeInvocation方法缎谷,其中省略了一些不重要的代碼片段井濒。
protected InterceptorStatusToken beforeInvocation(Object object) {
//根據(jù)SecurityMetadataSource獲取配置的權(quán)限屬性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//...
//判斷是否需要對認(rèn)證實(shí)體重新認(rèn)證,默認(rèn)為否
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//決策管理器開始決定是否授權(quán)列林,如果授權(quán)失敗,直接拋出AccessDeniedException
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
}
上面代碼可以看出希痴,第一步是根據(jù)SecurityMetadataSource獲取配置的權(quán)限屬性者甲,accessDecisionManager會(huì)用到權(quán)限列表信息。然后判斷是否需要對認(rèn)證實(shí)體重新認(rèn)證砌创,默認(rèn)為否虏缸。第二步是接著決策管理器開始決定是否授權(quán)鲫懒,如果授權(quán)失敗,直接拋出AccessDeniedException刽辙。
(1). 獲取配置的權(quán)限屬性
public class SecureResourceFilterInvocationDefinitionSource implements FilterInvocationSecurityMetadataSource, InitializingBean {
private PathMatcher matcher;
//map保存配置的URL對應(yīng)的權(quán)限集
private static Map<String, Collection<ConfigAttribute>> map = new HashMap<>();
//根據(jù)傳入的對象URL進(jìn)行循環(huán)
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
logger.info("getAttributes");
//應(yīng)該做instanceof
FilterInvocation filterInvocation = (FilterInvocation) o;
//String method = filterInvocation.getHttpRequest().getMethod();
String requestURI = filterInvocation.getRequestUrl();
//循環(huán)資源路徑窥岩,當(dāng)訪問的Url和資源路徑url匹配時(shí),返回該Url所需要的權(quán)限
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;
}
//...
//設(shè)置權(quán)限集宰缤,即上述的map
@Override
public void afterPropertiesSet() throws Exception {
logger.info("afterPropertiesSet");
//用來匹配訪問資源路徑
this.matcher = new AntPathMatcher();
//可以有多個(gè)權(quán)限
Collection<ConfigAttribute> atts = new ArrayList<>();
ConfigAttribute c1 = new SecurityConfig("ROLE_ADMIN");
atts.add(c1);
map.put("/oauth/check_token", atts);
}
}
上面是getAttributes()實(shí)現(xiàn)的具體細(xì)節(jié)颂翼,將請求的URL取出進(jìn)行匹配事先設(shè)定的受限資源,最后返回需要的權(quán)限慨灭、角色朦乏。系統(tǒng)在啟動(dòng)的時(shí)候就會(huì)讀取到配置的map集合,對于攔截到請求進(jìn)行匹配氧骤。代碼中注釋比較詳細(xì)呻疹,這邊不多說。
(2). 決策管理器
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();
//判斷用戶所擁有的權(quán)限语淘,是否符合對應(yīng)的Url權(quán)限诲宇,如果實(shí)現(xiàn)了UserDetailsService际歼,則用戶權(quán)限是loadUserByUsername返回用戶所對應(yīng)的權(quán)限
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!");
}
//...
}
上面的代碼是決策管理器的實(shí)現(xiàn)惶翻,其邏輯也比較簡單,將請求所具有的權(quán)限與設(shè)定的受限資源所需的進(jìn)行匹配鹅心,如果具有則返回吕粗,否則拋出沒有正確的權(quán)限異常。默認(rèn)提供的決策管理器有三種旭愧,分別為AffirmativeBased颅筋、ConsensusBased、UnanimousBased输枯,篇幅有限议泵,我們這邊不再擴(kuò)展了。
補(bǔ)充一下桃熄,所具有的權(quán)限是通過之前配置的認(rèn)證方式先口,有password認(rèn)證和client認(rèn)證兩種。我們之前在授權(quán)服務(wù)器中配置了withClientDetails
瞳收,所以用frontend身份驗(yàn)證獲得的權(quán)限是我們預(yù)先配置在數(shù)據(jù)庫中的authorities碉京。
4. 總結(jié)
Auth系統(tǒng)主要功能是授權(quán)認(rèn)證和鑒權(quán)。項(xiàng)目微服務(wù)化后螟深,原有的單體應(yīng)用基于HttpSession認(rèn)證鑒權(quán)不能滿足微服務(wù)架構(gòu)下的需求谐宙。每個(gè)微服務(wù)都需要對訪問進(jìn)行鑒權(quán),每個(gè)微應(yīng)用都需要明確當(dāng)前訪問用戶以及其權(quán)限界弧,尤其當(dāng)有多個(gè)客戶端凡蜻,包括web端搭综、移動(dòng)端等等,單體應(yīng)用架構(gòu)下的鑒權(quán)方式就不是特別合適了划栓。權(quán)限服務(wù)作為基礎(chǔ)的公共服務(wù)设凹,也需要微服務(wù)化。
筆者的設(shè)計(jì)中茅姜,Auth服務(wù)一方面進(jìn)行授權(quán)認(rèn)證闪朱,另一方面是基于token進(jìn)行身份合法性和API級(jí)別的權(quán)限校驗(yàn)。對于某個(gè)服務(wù)的請求钻洒,經(jīng)過網(wǎng)關(guān)會(huì)調(diào)用Auth服務(wù)奋姿,對token合法性進(jìn)行驗(yàn)證。同時(shí)筆者根據(jù)當(dāng)前項(xiàng)目的整體情況素标,存在部分遺留服務(wù)称诗,這些遺留服務(wù)又沒有足夠的時(shí)間和人力立馬進(jìn)行微服務(wù)改造,而且還需要繼續(xù)運(yùn)行头遭。為了適配當(dāng)前新的架構(gòu)寓免,采取的方案就是對這些遺留服務(wù)的操作API,在Auth服務(wù)進(jìn)行API級(jí)別的操作權(quán)限鑒定计维。API級(jí)別的操作權(quán)限校驗(yàn)需要的上下文信息需要結(jié)合業(yè)務(wù)袜香,與客戶端進(jìn)行商定,應(yīng)該在token能取到相應(yīng)信息鲫惶,傳遞給Auth服務(wù)蜈首,不過應(yīng)盡量減少在header取上下文校驗(yàn)的信息。
筆者將本次開發(fā)Auth系統(tǒng)所涉及的大部分代碼及源碼進(jìn)行了解析欠母,至于沒有講到的一些內(nèi)容和細(xì)節(jié)欢策,讀者可以自行擴(kuò)展。
5. 不足與后續(xù)工作
5.1 存在的不足
-
API級(jí)別操作權(quán)限校驗(yàn)的通用性
(1). 對于API級(jí)別操作權(quán)限校驗(yàn)赏淌,需要在網(wǎng)關(guān)處調(diào)用時(shí)構(gòu)造相應(yīng)的上下文信息踩寇。上下文信息基本依賴于 token中的payload,如果信息太多引起token太長六水,導(dǎo)致每次客戶端的請求頭部長度變長俺孙。
(2). 并不是所有的操作接口都能覆蓋到,這個(gè)問題是比較嚴(yán)重的缩擂,根據(jù)上下文集合很可能出現(xiàn)好多接口 的權(quán)限沒法鑒定鼠冕,最后的結(jié)果就是API級(jí)別操作權(quán)限校驗(yàn)失敗的是絕對沒有權(quán)限訪問該接口,而通過不一定能訪問胯盯,因?yàn)樵摻涌谏婕暗降纳舷挛母緵]法完全得到懈费。我們的項(xiàng)目在現(xiàn)階段,定義的最小上下文集合能勉強(qiáng)覆蓋到博脑,但是對于后面擴(kuò)增的服務(wù)接口真的是不樂觀憎乙。
(3). 每個(gè)服務(wù)的每個(gè)接口都在Auth服務(wù)注冊其所需要的權(quán)限票罐,太過麻煩,Auth服務(wù)需要額外維護(hù)這樣的信息泞边。
-
網(wǎng)關(guān)處調(diào)用Auth服務(wù)帶來的系統(tǒng)吞吐量瓶頸
(1). 這個(gè)其實(shí)很容易理解该押,Auth服務(wù)作為公共的基礎(chǔ)服務(wù),大多數(shù)服務(wù)接口都會(huì)需要鑒權(quán)阵谚,Auth服務(wù)需要經(jīng)過復(fù)雜蚕礼。
(2). 網(wǎng)關(guān)調(diào)用Auth服務(wù),阻塞調(diào)用梢什,只有等Auth服務(wù)返回校驗(yàn)結(jié)果奠蹬,才會(huì)做進(jìn)一步處理。雖說Auth服務(wù)可以多實(shí)例部署嗡午,但是并發(fā)量大了之后囤躁,其瓶頸明顯可見,嚴(yán)重可能會(huì)造成整個(gè)系統(tǒng)的不可用荔睹。
5.2 后續(xù)工作
- 從整個(gè)系統(tǒng)設(shè)計(jì)角度來講狸演,API級(jí)別操作權(quán)限后期將會(huì)分散在各個(gè)服務(wù)的接口上,由各個(gè)接口負(fù)責(zé)其所需要的權(quán)限僻他、身份等宵距。Spring Security對于接口級(jí)別的權(quán)限校驗(yàn)也是支持的,之所以采用這樣的做法中姜,也是為了兼容新服務(wù)和遺留的服務(wù)消玄,主要是針對遺留服務(wù),新的服務(wù)采用的是分散在各個(gè)接口之上丢胚。
- 將API級(jí)別操作權(quán)限分散到各個(gè)服務(wù)接口之后,相應(yīng)的能提升Auth服務(wù)的響應(yīng)受扳。網(wǎng)關(guān)能夠及時(shí)的對請求進(jìn)行轉(zhuǎn)發(fā)或者拒絕携龟。
- API級(jí)別操作權(quán)限所需要的上下文信息對各個(gè)接口真的設(shè)計(jì)的很復(fù)雜,這邊我們確實(shí)花了時(shí)間勘高,同時(shí)管理移動(dòng)服務(wù)的好幾百操作接口所對應(yīng)的權(quán)限峡蟋,非常煩。华望!
本文的源碼地址:
GitHub:https://github.com/keets2012/Auth-service
碼云: https://gitee.com/keets/Auth-Service
訂閱最新文章蕊蝗,歡迎關(guān)注我的公眾號(hào)
參考
- 配置表單登錄
- Spring Security3源碼分析-FilterSecurityInterceptor分析
- Core Security Filters
- Spring Security(四)--核心過濾器源碼分析
相關(guān)閱讀
認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(一)
認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(二)
認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(三)