玩轉(zhuǎn)SpringCloud Security OAuth2資源授權(quán)動(dòng)態(tài)權(quán)限擴(kuò)展
點(diǎn)擊關(guān)注公眾號,Java干貨及時(shí)送達(dá)??

來源:blog.csdn.net/new_com/article/
details/104731154
在Spring Cloud Security 中,認(rèn)證和授權(quán)都是通過FilterChainProxy(Servlet Filter過濾器)攔截然后進(jìn)行操作的。FilterSecurityInterceptor過濾器攔截了受保護(hù)資源的請求,然后進(jìn)行授權(quán)處理,授權(quán)驗(yàn)證的邏輯在其父類AbstractSecurityInterceptor實(shí)現(xiàn)。大致流程如下:
使用SecurityMetadataSource根據(jù)http請求獲取對應(yīng)擁有的權(quán)限。 使用Spring Security授權(quán)模塊對用戶訪問的資源進(jìn)行授權(quán)驗(yàn)證。
AbstractSecurityInterceptor的部分源碼如下:
????//?AbstractSecurityInterceptor.java
?protected?InterceptorStatusToken?beforeInvocation(Object?object)?{
????????......
?
????????//?根據(jù)http請求獲取對應(yīng)的配置的權(quán)限信息
??Collection?attributes?=?this.obtainSecurityMetadataSource()
????.getAttributes(object);
?
?????......
????????//?對用戶認(rèn)證進(jìn)行校驗(yàn)
??Authentication?authenticated?=?authenticateIfRequired();
??try?{
????????????//?對用戶的權(quán)限與訪問資源擁有的權(quán)限進(jìn)行校驗(yàn)
???this.accessDecisionManager.decide(authenticated,?object,?attributes);
??}
??catch?(AccessDeniedException?accessDeniedException)?{
???publishEvent(new?AuthorizationFailureEvent(object,?attributes,?authenticated,
?????accessDeniedException));
?
???throw?accessDeniedException;
??}
????????......
?}
在默認(rèn)的SecurityMetadataSource的子類DefaultFilterInvocationSecurityMetadataSource實(shí)現(xiàn)中,會把資源服務(wù)器配置的權(quán)限信息全部加載到內(nèi)存。如果要實(shí)現(xiàn)授權(quán)權(quán)限的動(dòng)態(tài)修改,需要擴(kuò)展SecurityMetadataSource,例如,使權(quán)限數(shù)據(jù)能夠動(dòng)態(tài)的從數(shù)據(jù)庫獲取。并且,自定義根據(jù)動(dòng)態(tài)權(quán)限認(rèn)證邏輯AccessDecisionVoter。
擴(kuò)展SecurityMetadataSource
自定義PermissionFilterInvocationSecurityMetadataSource,參考默認(rèn)的DefaultFilterInvocationSecurityMetadataSource實(shí)現(xiàn)從數(shù)據(jù)庫動(dòng)態(tài)的根據(jù)訪問http請求獲取配置的權(quán)限。由于每次都需要獲取全部的有效的權(quán)限配置數(shù)據(jù),可以對權(quán)限數(shù)據(jù)做一個(gè)本地緩存,提交查詢效率。
在ConfigAttribute的子類實(shí)現(xiàn)中,可以使用SecurityConfig保存配置的權(quán)限. 訪問規(guī)則ConfigAttribute。實(shí)現(xiàn)代碼如下:
public?class?PermissionFilterInvocationSecurityMetadataSource?implements?FilterInvocationSecurityMetadataSource?{
?
????private?final?PermissionClient?permissionClient;
?
????public?PermissionFilterInvocationSecurityMetadataSource(PermissionClient?permissionClient)?{
????????this.permissionClient?=?permissionClient;
????}
?
????/**
?????*?轉(zhuǎn)換權(quán)限列表
?????*/
????private?Map>?requestMatcherCollectionMap()?{
?
????????List?allPermissions?=?permissionClient.findAllList();
????????if?(CollectionUtils.isEmpty(allPermissions))?{
????????????return?ImmutableMap.of();
????????}
????????return?allPermissions.stream()
????????????????.collect(Collectors.toMap(permission?->?new?AntPathRequestMatcher(permission.getUrl()),
????????????????????????permission?->?Lists.newArrayList(new?SecurityConfig(permission.getCode()))));
????}
?
?
????@Override
????public?Collection?getAttributes(Object?object)?throws?IllegalArgumentException? {
?
????????final?HttpServletRequest?request?=?((FilterInvocation)?object).getRequest();
????????for?(Map.Entry>?entry?:?requestMatcherCollectionMap().entrySet())?{
????????????if?(entry.getKey().matches(request))?{
????????????????return?entry.getValue();
????????????}
????????}
????????return?null;
????}
?
????@Override
????public?Collection?getAllConfigAttributes()? {
????????return?requestMatcherCollectionMap().values()
????????????????.stream().flatMap(Collection::stream)
????????????????.collect(Collectors.toList());
????}
?
????@Override
????public?boolean?supports(Class>?clazz)?{
????????return?FilterInvocation.class.isAssignableFrom(clazz);
????}
}
擴(kuò)展根據(jù)權(quán)限授權(quán)邏輯
自定義PermissionsVoter類實(shí)現(xiàn)AccessDecisionVoter接口,實(shí)現(xiàn)了用戶只要擁有訪問資源的權(quán)限就可以訪問。參考RoleVoter具體的實(shí)現(xiàn)邏輯,代碼如下:
public?class?PermissionsVoter?implements?AccessDecisionVoter<Object>?{
?
????@Override
????public?boolean?supports(ConfigAttribute?attribute)?{
????????return?Objects.nonNull(attribute.getAttribute());
????}
?
????@Override
????public?boolean?supports(Class>?clazz)?{
????????return?true;
????}
?
????@Override
????public?int?vote(Authentication?authentication,?Object?object,?Collection?attributes) ?{
?
????????if?(CollectionUtils.isEmpty(attributes))?{
????????????return?ACCESS_DENIED;
????????}
????????//?用戶授權(quán)的權(quán)限
????????Collection?extends?GrantedAuthority>?grantedAuthorities;
????????if?(Objects.isNull(authentication)
????????????????||?CollectionUtils.isEmpty(grantedAuthorities?=?extractAuthorities(authentication))
????????????????||?Objects.isNull(object))?{
?
????????????log.info("user?no?authentication!");
????????????return?ACCESS_DENIED;
????????}
????????for?(GrantedAuthority?grantedAuthority?:?grantedAuthorities)?{
?
????????????String?authority;
????????????if?(StringUtils.isNotBlank(authority?=?grantedAuthority.getAuthority())
????????????????????&&?match(authority,?attributes))?{
????????????????return?ACCESS_GRANTED;
????????????}
????????}
????????return?ACCESS_DENIED;
????}
?
????private?boolean?match(String?authority,?Collection?attributes) ?{
?
????????for?(ConfigAttribute?configAttribute?:?attributes)?{
????????????String?attribute;
????????????if?(StringUtils.isNotBlank(attribute?=?configAttribute.getAttribute())
????????????????????&&?attribute.equals(authority))?{
????????????????return?true;
????????????}
????????}
????????return?false;
????}
?
????/**
?????*?獲取用戶權(quán)限列表
?????*/
????Collection?extends?GrantedAuthority>?extractAuthorities(Authentication?authentication)?{
????????return?authentication.getAuthorities();
????}
}
配置資源服務(wù)器
在配置資源服務(wù)器,主要是如下配置:
SecurityMetadataSource,獲取資源權(quán)限的設(shè)置 AccessDecisionManager,自定義授權(quán)邏輯的配置
重點(diǎn)講解下自定義AccessDecisionManager的情況,
選擇AffirmativeBased(只要有一個(gè)授權(quán)處理通過則可以進(jìn)行訪問)。 配置RoleVoter(角色授權(quán)),AuthenticatedVoter(認(rèn)證信息授權(quán)), WebExpressionVoter(EL描述授權(quán))spring security默認(rèn)的授權(quán)邏輯。 重點(diǎn)講解WebExpressionVoter的初始化。在生成WebExpressionVoter時(shí),需要設(shè)置其expressionHandler為 OAuth2WebSecurityExpressionHandler,這樣在進(jìn)行驗(yàn)證時(shí)才不會報(bào)錯(cuò)。在使用默認(rèn)的AccessDecisionManager啟動(dòng)進(jìn)行驗(yàn)證時(shí),Spring Security使用ExpressionUrlAuthorizationConfigurer默認(rèn)配置WebExpressionVoter,并且在設(shè)置expressionHandler為OAuth2WebSecurityExpressionHandler。使用默認(rèn)配置資源服務(wù)器啟動(dòng)時(shí),調(diào)試的結(jié)果如下:

在資源資源服務(wù)器中的詳細(xì)配置如下:
@Configuration
@EnableResourceServer
public?class?ResourceServerConfig?extends?ResourceServerConfigurerAdapter?{
?
?
????/**
?????*?資源服務(wù)器內(nèi)的資源訪問控制
?????*/
????@Override
????public?void?configure(HttpSecurity?http)?throws?Exception?{
?
????????http.authorizeRequests()
????????????????.antMatchers("/webjars/**",?"/v2/api-docs",?"/swagger-resources/**",?"/swagger-ui.html",?"/swagger.json").permitAll()
????????????????.anyRequest().authenticated()
????????????????.withObjectPostProcessor(new?ObjectPostProcessor()?{
?
????????????????????@Override
????????????????????public??O?postProcess(O?fsi)?{
//?權(quán)限獲取自定義配置
????????????????????????fsi.setSecurityMetadataSource(new?PermissionFilterInvocationSecurityMetadataSource(permissionClient));
????????????????????????return?fsi;
????????????????????}
????????????????})
????????????????.accessDecisionManager(accessDecisionManager());
????}
?
????private?AccessDecisionManager?accessDecisionManager()?{
?
????????WebExpressionVoter?webExpressionVoter?=?new?WebExpressionVoter();
????????webExpressionVoter.setExpressionHandler(new?OAuth2WebSecurityExpressionHandler());
????????//?授權(quán)邏輯自定義配置
????????return?new?AffirmativeBased(Lists.newArrayList(new?PermissionsVoter(),?new?RoleVoter(),
????????????????new?AuthenticatedVoter(),?webExpressionVoter));
????}
}
授權(quán)測試
在db中配置用戶username為admin,password為123456的用戶擁有couponDemo的訪問權(quán)限,在使用postman先認(rèn)證,然后攜帶訪問coupon/demo api,結(jié)果正常返回,操作如圖:

在訪問時(shí),調(diào)用自定義PermissionFilterInvocationSecurityMetadataSource獲取配置權(quán)限的截圖如下:

在進(jìn)行授權(quán)處理時(shí),調(diào)用自定義 PermissionsVoter進(jìn)行授權(quán)認(rèn)證,截圖如下:

不足與優(yōu)化之處
一般的實(shí)現(xiàn)中,在每個(gè)單獨(dú)的微服務(wù)中配置資源服務(wù)器,資源授權(quán)成功以后,SecurityContextPersistenceFilter已經(jīng)把當(dāng)前登錄用戶信息存儲到SecurityContextHolder上下文,直接根據(jù)security提供的SecurityContextHolder.getContext().getAuthentication().getPrincipal()就可以獲取當(dāng)前登錄用戶信息。如果,微服務(wù)不斷地增加,一般常見的電商系統(tǒng)都有用戶服務(wù),商品服務(wù),訂單服務(wù)等等,這時(shí),該如何配置資源服務(wù)器呢?大家可以思考一下。
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

