OAuth2授权概述
在Spring Cloud Security 中,认证和授权都是通过FilterChainProxy(Servlet Filter过滤器)拦截然后进行操作的。在Spring Security中FilterSecurityInterceptor 过滤器会对资源受保护的Http请求进行拦截,然后进行授权处理。其部分源码如下:
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
/**
* 授权过滤器拦截逻辑
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
/**
* 设置权限信息获取服务FilterInvocationSecurityMetadataSource
*/
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 授权逻辑校验
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 调用下一个过滤器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
// 资源服务器的访问
super.finallyInvocation(token);
}
// 调用结束后的处理
super.afterInvocation(token, null);
}
}
/**
* Indicates whether once-per-request handling will be observed. By default this is
* <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only
* execute once-per-request. Sometimes users may wish it to execute more than once per
* request, such as when JSP forwards are being used and filter security is desired on
* each included fragment of the HTTP request.
*
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise
* <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
* authorizations for each and every fragment of the HTTP request.
*/
public boolean isObserveOncePerRequest() {
return observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
FilterSecurityInterceptor 拦截处理的大致流程如下:
- 处理授权逻辑校验
- 调用余下的过滤器
- 授权成功后,访问真正的资源服务器请求。
在第一步授权逻辑的校验逻辑中,调用的是在父类的AbstractSecurityInterceptor的beforeInvocation方法实现的,大致流程如下:
- 使用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;
}
......
}
资源服务器配置权限获取逻辑
在FilterSecurityInterceptor中FilterInvocationSecurityMetadataSource,用于获取资源拥有的授权信息。在其默认子类DefaultFilterInvocationSecurityMetadataSource 实现类中的源码如下:
public class DefaultFilterInvocationSecurityMetadataSource implements
FilterInvocationSecurityMetadataSource {
/**
* 请求与拥有权限的映射
*/
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
/**
* 获取资源服务器拥有的全部权限
*/
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<>();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
allAttributes.addAll(entry.getValue());
}
return allAttributes;
}
/**
* 根据请求获取资源服务器拥有的权限
*/
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
授权处理逻辑
在Spring Security中,对于授权处理的逻辑,通过AccessDecisionManager接口实现的,源码如下:
public interface AccessDecisionManager {
/**
* authentication 认证以后拥有的权限
* object 授权的Url信息
* configAttributes url路径权限属性
*/
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
AccessDecisionManager的实现类自定义授权逻辑的具体实现,其常见的实现类如下:
- AffirmativeBased:只要有一个授权处理通过则可以进行访问(默认使用的类)。
- ConsensusBased:根据少数服务多数的原则进行判断。
- UnanimousBased:只要有一个授权不通过,则不能访问。
AccessDecisionManager的公共子类AbstractAccessDecisionManager中包含一个AccessDecisionVoter列表,用于组合处理授权逻辑,AccessDecisionVoter接口定义如下:
public interface AccessDecisionVoter<S> {
// 授权通过
int ACCESS_GRANTED = 1;
// 授权忽略
int ACCESS_ABSTAIN = 0;
// 授权拒绝
int ACCESS_DENIED = -1;
/**
* 支持的路径授权属性
*/
boolean supports(ConfigAttribute attribute);
/**
*支持的类
*/
boolean supports(Class<?> clazz);
/**
* 授权方法,authentication为用户认证过后的认证信息,object为url路径信息,attributes为路径配置的授权信息
*/
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
AccessDecisionVoter的常见的实现类如下:
- RoleVoter(根据角色授权处理)
- AuthenticatedVoter(认证授权处理)
- webExpressionVoter(描述语言的授权处理)
资源服务器的搭建
在Spring Cloud Security资源服务器的搭建中,通过注解@EnableResourceServer开启资源服务器的默认配置,可以继承ResourceServerConfigurerAdapter自定义资源服务器的逻辑。主要有两方面的配置:
- ResourceServerSecurityConfigurer,用于配置资源服务器的安全配置,例如,访问令牌的校验。
- HttpSecurity,用于配置资源服务器授权逻辑。例如,拥有的权限配置,授权逻辑的自定义。
个人demo实现中,使用redis存储token,RemoteTokenServices远程服务调用方式访问认证服务器的check_token方法,jwt方式进行token转换,静态的配置了访问资源的权限,源码如下:
public class Oauth2ResourcesConfig extends ResourceServerConfigurerAdapter {
/**
* redis连接工厂
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* jwt
*/
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
*
*/
@Autowired
private RestTemplate restTemplate;
/**
* 资源服务器安全配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setAccessTokenConverter(jwtAccessTokenConverter);
tokenServices.setRestTemplate(restTemplate);
tokenServices.setClientId("meituan");
tokenServices.setClientSecret("123456");
// TODO 本地启动使用 RestTemplate通过服务名会访问80端口
// tokenServices.setCheckTokenEndpointUrl("http://oauth2-server/oauth/check_token");
tokenServices.setCheckTokenEndpointUrl("http://localhost:8766/oauth/check_token");
resources
.resourceId("coupon")
.tokenStore(new RedisTokenStore(redisConnectionFactory))
.tokenServices(tokenServices)
// 访问无状态
.stateless(true);
}
@Autowired
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 资源服务器内的资源访问控制
*/
@Override
public void configure(HttpSecurity http) throws Exception {
// session配置,微服务中配置为无状态
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 授权配置
.and().authorizeRequests()
// 无需认证授权即可访问
.antMatchers("/coupon/demo2", "/coupon/demo3").permitAll()
// 角色设置
.antMatchers("/user/**").hasAnyRole("user")
// 权限设置
.antMatchers("/coupon/demo").hasAuthority("couponDemo")
// 剩余所有请求都需要身份认证才能访问
.anyRequest().authenticated();
}
/**
* jwt token 配置
*/
@Configuration
public static class JwtTokenConfig {
/**
* JwtAccessTokenConverter
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("kuqi-mall");
return converter;
}
}
}
授权测试
测试无需授权的api “/coupon/demo2″和 “/coupon/demo3″,直接访问”/coupon/demo2″和 “/coupon/demo3″地址,无需token,可以直接放回结果:
测试访问”/coupon/demo”,需要进行身份认证,并且带有couponDemo权限才可以访问。在db中配置user(用户),role(角色),permission(权限)的关系数据,配置了用户username为admin,password为123456,拥有couponDemo的权限。首先获取用户的访问授权码access token,流程如下:
根据返回的访问授权码,访问”/coupon/demo”,成功返回测试数据,流程如下:
不足与优化之处
Spring Cloud Security资源服务器的授权处理,在以上的示例中属于在静态加载,在启动资源服务时,会全部加到内存。在资源服务器运行期间,如果需要修改资源拥有的权限,该如何处理呢?请关注后续的Spring Cloud Security动态权限配置章节。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13630.html