资源服务器关键类
写本篇笔记的原因:
在搭建资源服务器时,对资源服务器是如何校验用户的权限和接口的权限是否匹配存在疑惑,在似懂非懂地看了些源码后,在此做个笔记来记录OAuth2资源服务器大体上是如何做权限校验的工作的
在本次学习中,收获了两点
-
掌握了一个技能:如何跟着接口的运行来知道它整个源码是如何运行的
这里有个小技巧,将Spring的日志级别设置成DEBUG,便可以知道代码执行过程中都调用了哪些类,通过打断点的方式知道每一步主要是在做什么事情的
logging: level: org.springframework: DEBUG
-
了解到了资源服务器是怎么做接口权限校验的
话不多说,马上进入重点
OAuth2资源服务器权限校验重点主要为下面四个类,先列举下四个类及它们的主要作用
-
OAuth2AuthenticationProcessingFilter(1.获取authentication2.将调用OAuth2AuthenticationManager解析结果出来的结果放在Security的上下文中)
-
OAuth2AuthenticationManager(1.校验token的有效性-调用DefaultTokenServices去解析token2.如果用户传的details-用户信息与token解析出来的不符合,将token解析出来的用户信息作为参考标准)
-
DefaultTokenServices(1.根据配置的token解析器解析token,获取用户信息)
-
AbstractSecurityInterceptor - beforeInvocation (1.获得接口的权限2.校验用户权限和接口权限是否匹配-此步操作在OAuth2AuthenticationProcessingFilter校验通过后进行)
资源服务器是如何验证接口和用户权限是否匹配的?-在AbstractSecurityInterceptor中做的校验
接下来,到源码中看看他们究竟是怎么运行的(目前是代码中主要的流程,还有许多细节没有考虑和研究)
OAuth2AuthenticationProcessingFilter
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
// other methods
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
// 获取authentication信息
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
// 获取用户设置的details(用户信息)
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
// 校验token的有效性
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
eventPublisher.publishAuthenticationSuccess(authResult);
// 校验成功 设置到Security上下文中
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}
chain.doFilter(request, response);
}
}
OAuth2AuthenticationManager
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
// other methods
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
// 获取token
String token = (String) authentication.getPrincipal();
// 解析token
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}
checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
// 用户信息与token解析出来的不符合,将token解析出来的用户信息作为参考标准
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;
}
private void checkClientDetails(OAuth2Authentication auth) {
if (clientDetailsService != null) {
ClientDetails client;
try {
client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());
}
catch (ClientRegistrationException e) {
throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");
}
Set<String> allowed = client.getScope();
for (String scope : auth.getOAuth2Request().getScope()) {
if (!allowed.contains(scope)) {
throw new OAuth2AccessDeniedException(
"Invalid token contains disallowed scope (" + scope + ") for this client");
}
}
}
}
}
DefaultTokenServices
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
// other methods
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
// 根据配置的tokenStore解析token
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}
// 获取用户信息
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
// in case of race condition
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
}
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
return result;
}
}
AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
// other methods
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 获取接口的权限 object为对应的接口
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 校验用户的权限和接口的需要的权限是否匹配 调用AffirmativeBased这个类去做校验
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
}
网友评论