玩转SpringCloud Security OAuth2资源授权动态权限扩展
Posted Java知音_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转SpringCloud Security OAuth2资源授权动态权限扩展相关的知识,希望对你有一定的参考价值。
点击关注公众号,实用技术文章及时了解
来源:blog.csdn.net/new_com/article/
details/104731154
在Spring Cloud Security 中,认证和授权都是通过FilterChainProxy(Servlet Filter过滤器)拦截然后进行操作的。FilterSecurityInterceptor过滤器拦截了受保护资源的请求,然后进行授权处理,授权验证的逻辑在其父类AbstractSecurityInterceptor
实现。大致流程如下:
使用SecurityMetadataSource根据http请求获取对应拥有的权限。
使用Spring Security授权模块对用户访问的资源进行授权验证。
AbstractSecurityInterceptor的部分源码如下:
// AbstractSecurityInterceptor.java
protected InterceptorStatusToken beforeInvocation(Object object)
......
// 根据http请求获取对应的配置的权限信息
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
......
// 对用户认证进行校验
Authentication authenticated = authenticateIfRequired();
try
// 对用户的权限与访问资源拥有的权限进行校验
this.accessDecisionManager.decide(authenticated, object, attributes);
catch (AccessDeniedException accessDeniedException)
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
......
在默认的SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource
实现中,会把资源服务器配置的权限信息全部加载到内存。如果要实现授权权限的动态修改,需要扩展SecurityMetadataSource,例如,使权限数据能够动态的从数据库获取。并且,自定义根据动态权限认证逻辑AccessDecisionVoter。
扩展SecurityMetadataSource
自定义PermissionFilterInvocationSecurityMetadataSource
,参考默认的DefaultFilterInvocationSecurityMetadataSource
实现从数据库动态的根据访问http请求获取配置的权限。由于每次都需要获取全部的有效的权限配置数据,可以对权限数据做一个本地缓存,提交查询效率。
在ConfigAttribute的子类实现中,可以使用SecurityConfig保存配置的权限. 访问规则ConfigAttribute。实现代码如下:
public class PermissionFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource
private final PermissionClient permissionClient;
public PermissionFilterInvocationSecurityMetadataSource(PermissionClient permissionClient)
this.permissionClient = permissionClient;
/**
* 转换权限列表
*/
private Map<RequestMatcher, Collection<ConfigAttribute>> requestMatcherCollectionMap()
List<Permission> 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<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMatcherCollectionMap().entrySet())
if (entry.getKey().matches(request))
return entry.getValue();
return null;
@Override
public Collection<ConfigAttribute> getAllConfigAttributes()
return requestMatcherCollectionMap().values()
.stream().flatMap(Collection::stream)
.collect(Collectors.toList());
@Override
public boolean supports(Class<?> clazz)
return FilterInvocation.class.isAssignableFrom(clazz);
扩展根据权限授权逻辑
自定义PermissionsVoter类实现AccessDecisionVoter接口,实现了用户只要拥有访问资源的权限就可以访问。参考RoleVoter具体的实现逻辑,代码如下:
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<ConfigAttribute> attributes)
if (CollectionUtils.isEmpty(attributes))
return ACCESS_DENIED;
// 用户授权的权限
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<ConfigAttribute> attributes)
for (ConfigAttribute configAttribute : attributes)
String attribute;
if (StringUtils.isNotBlank(attribute = configAttribute.getAttribute())
&& attribute.equals(authority))
return true;
return false;
/**
* 获取用户权限列表
*/
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication)
return authentication.getAuthorities();
配置资源服务器
在配置资源服务器,主要是如下配置:
SecurityMetadataSource,获取资源权限的设置
AccessDecisionManager,自定义授权逻辑的配置
重点讲解下自定义AccessDecisionManager的情况,
选择AffirmativeBased(只要有一个授权处理通过则可以进行访问)。
配置RoleVoter(角色授权),AuthenticatedVoter(认证信息授权), WebExpressionVoter(EL描述授权)spring security默认的授权逻辑。
重点讲解WebExpressionVoter的初始化。在生成WebExpressionVoter时,需要设置其expressionHandler为
OAuth2WebSecurityExpressionHandler
,这样在进行验证时才不会报错。在使用默认的AccessDecisionManager启动进行验证时,Spring Security使用ExpressionUrlAuthorizationConfigurer
默认配置WebExpressionVoter,并且在设置expressionHandler为OAuth2WebSecurityExpressionHandler
。使用默认配置资源服务器启动时,调试的结果如下:
在资源资源服务器中的详细配置如下:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
/**
* 资源服务器内的资源访问控制
*/
@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<FilterSecurityInterceptor>()
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O fsi)
// 权限获取自定义配置
fsi.setSecurityMetadataSource(new PermissionFilterInvocationSecurityMetadataSource(permissionClient));
return fsi;
)
.accessDecisionManager(accessDecisionManager());
private AccessDecisionManager accessDecisionManager()
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(new OAuth2WebSecurityExpressionHandler());
// 授权逻辑自定义配置
return new AffirmativeBased(Lists.newArrayList(new PermissionsVoter(), new RoleVoter(),
new AuthenticatedVoter(), webExpressionVoter));
授权测试
在db中配置用户username为admin,password为123456的用户拥有couponDemo的访问权限,在使用postman先认证,然后携带访问coupon/demo api
,结果正常返回,操作如图:
在访问时,调用自定义PermissionFilterInvocationSecurityMetadataSource
获取配置权限的截图如下:
在进行授权处理时,调用自定义 PermissionsVoter进行授权认证,截图如下:
不足与优化之处
一般的实现中,在每个单独的微服务中配置资源服务器,资源授权成功以后,SecurityContextPersistenceFilter
已经把当前登录用户信息存储到SecurityContextHolder上下文,直接根据security提供的SecurityContextHolder.getContext().getAuthentication().getPrincipal()
就可以获取当前登录用户信息。如果,微服务不断地增加,一般常见的电商系统都有用户服务,商品服务,订单服务等等,这时,该如何配置资源服务器呢?大家可以思考一下。
推荐:
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
以上是关于玩转SpringCloud Security OAuth2资源授权动态权限扩展的主要内容,如果未能解决你的问题,请参考以下文章