Spring Security OAuth2源码解析(一)

论坛 期权论坛 脚本     
已经匿名di用户   2022-4-13 16:43   1707   0

目录

引入

AuthorizationServerEndpointsConfiguration

属性

AuthorizationEndpoint

OAuth2RequestFactory

DefaultOAuth2RequestFactory

TokenEndpoint

TokenGranter

AuthorizationServerTokenServices

DefaultTokenServices

TokenStore

AuthorizationServerSecurityConfiguration

HttpSecurity

ClientDetailsServiceConfiguration

AuthorizationServerEndpointsConfiguration


引入

在引入OAuth2时,会在@Configuration注解的bean上添加@EnableAuthorizationServer注解。

@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
@Deprecated
public @interface EnableAuthorizationServer {

}

@EnableAuthorizationServer注解主要引入了2个类:AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration,分别用于配置授权端点和鉴权配置。

AuthorizationServerEndpointsConfiguration

属性

定义了端点配置,鉴权服务器器配置,以及client 信息服务。

 private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
 @Autowired
 private ClientDetailsService clientDetailsService;
 @Autowired
 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

AuthorizationEndpoint

public AuthorizationEndpoint authorizationEndpoint() throws Exception {
  AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
  FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
  authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
  authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
  authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
  authorizationEndpoint.setTokenGranter(tokenGranter());
  authorizationEndpoint.setClientDetailsService(clientDetailsService);
  authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
  authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
  authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
  authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
  authorizationEndpoint.setRedirectResolver(redirectResolver());
  return authorizationEndpoint;
 }

AuthorizationEndpoint 定义了

@RequestMapping(value = "/oauth/authorize")
 public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
   SessionStatus sessionStatus, Principal principal);
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
 public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
   SessionStatus sessionStatus, Principal principal)

GET请求:

 @RequestMapping(value = "/oauth/authorize")
 public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
   SessionStatus sessionStatus, Principal principal) {

  // 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象
  AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

  Set<String> responseTypes = authorizationRequest.getResponseTypes();
        //参数中需要包含token或者code。
  if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
   throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
  }
        //必须clientId
  if (authorizationRequest.getClientId() == null) {
   throw new InvalidClientException("A client id must be provided");
  }

  try {
            // 判断  principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,
            //如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,
            //最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因
  
   if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
    throw new InsufficientAuthenticationException(
      "User must be authenticated with Spring Security before authorization can be completed.");
   }

   ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

            //获取参数中的回调地址并且与系统配置的回调地址对比。用于判断client是否是合法,安全性校验。
   String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
   String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
   if (!StringUtils.hasText(resolvedRedirect)) {
    throw new RedirectMismatchException(
      "A redirectUri must be either supplied or preconfigured in the ClientDetails");
   }
   authorizationRequest.setRedirectUri(resolvedRedirect);


            //SCOPE
   oauth2RequestValidator.validateScope(authorizationRequest, client);

   
            //检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true)  )
   authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
     (Authentication) principal);
   // TODO: is this call necessary?
   boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
   authorizationRequest.setApproved(approved);


   if (authorizationRequest.isApproved()) {
                //隐式授权方式。
    if (responseTypes.contains("token")) {
     return getImplicitGrantResponse(authorizationRequest);
    }
                 //授权码方式。
    if (responseTypes.contains("code")) {
     return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
       (Authentication) principal));
    }
   }

   model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
   model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));

   return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

  }
  catch (RuntimeException e) {
   sessionStatus.setComplete();
   throw e;
  }

 }

POST请求:

@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
 public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
   SessionStatus sessionStatus, Principal principal) {

        //必须是Spring security验证了的
  if (!(principal instanceof Authentication)) {
   sessionStatus.setComplete();
   throw new InsufficientAuthenticationException(
     "User must be authenticated with Spring Security before authorizing an access token.");
  }
        //获取authorizationRequest参数
  AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST_ATTR_NAME);

  if (authorizationRequest == null) {
   sessionStatus.setComplete();
   throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
  }

  // 确保请求没被更改
  @SuppressWarnings("unchecked")
  Map<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME);
  if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) {
   throw new InvalidRequestException("Changes were detected from the original authorization request.");
  }

  try {
   Set<String> responseTypes = authorizationRequest.getResponseTypes();

   authorizationRequest.setApprovalParameters(approvalParameters);
            //验证
   authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
     (Authentication) principal);
            //验证结果
   boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
   authorizationRequest.setApproved(approved);

   if (authorizationRequest.getRedirectUri() == null) {
    sessionStatus.setComplete();
    throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
   }
            //重定向到拒绝访问。
   if (!authorizationRequest.isApproved()) {
    RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
      new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
      false, true, false);
    redirectView.setStatusCode(HttpStatus.SEE_OTHER);
    return redirectView;
   }
            //隐式模式。
   if (responseTypes.contains("token")) {
    return getImplicitGrantResponse(authorizationRequest).getView();
   }
            //授权码模式
   return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
  }
  finally {
   sessionStatus.setComplete();
  }

 }

OAuth2RequestFactory

OAuth2RequestFactory用于构造鉴权请求。

public interface OAuth2RequestFactory {
 AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters);
 OAuth2Request createOAuth2Request(AuthorizationRequest request);
 OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest);
 TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient);
 TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType);
}

DefaultOAuth2RequestFactory

DefaultOAuth2RequestFactoryOAuth2RequestFactory的默认实现。从参数中获取client_id,state,scope,redirect_uri,response_type,user_oauth_approval,grant_type的值,构造请求,并通过clientDetailsService获取client的ClientDetails。

TokenEndpoint

 @Bean
 public TokenEndpoint tokenEndpoint() throws Exception {
  TokenEndpoint tokenEndpoint = new TokenEndpoint();
  tokenEndpoint.setClientDetailsService(clientDetailsService);
  tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
  tokenEndpoint.setTokenGranter(tokenGranter());
  tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
  tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
  tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
  return tokenEndpoint;
 }

TokenEndpoint定义了访问令牌请求


 @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
 public ResponseEntity<OAuth2AccessToken> postAccessToken(
   Principal principal, @RequestParam Map<String, String> parameters)
   throws HttpRequestMethodNotSupportedException {
        // 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
  if (!(principal instanceof Authentication)) {
   throw new InsufficientAuthenticationException(
     "There is no client authentication. Try adding an appropriate authentication filter.");
  }

  String clientId = getClientId(principal);
  ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
        // 通过客户端信息生成 TokenRequest 对象
  TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

        //。。。。。。。省略很多验证。
        
  if (isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
   logger.debug("Clearing scope of incoming token request");
   tokenRequest.setScope(Collections.<String>emptySet());
  } else if (isRefreshTokenRequest(parameters)) {
   if (StringUtils.isEmpty(parameters.get("refresh_token"))) {
    throw new InvalidRequestException("refresh_token parameter not provided");
   }
            //refresh token有默认scopes,忽略fatory添加的。
   tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
  }
        // 调用 TokenGranter.grant()方法生成 OAuth2AccessToken 对象(即token)
  OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  if (token == null) {
   throw new UnsupportedGrantTypeException("Unsupported grant type");
  }

  return getResponse(token);
 }

TokenGranter

TokenGranter用于生成token。

public interface TokenGranter {
 OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);
}

主要对应4种实现方式,再加上刷新token。

标准逻辑:AbstractTokenGranter实现

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        //授权方式一致判断。
  if (!this.grantType.equals(grantType)) {
   return null;
  }
  
  String clientId = tokenRequest.getClientId();
  ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        //验证client的授权方式
  validateGrantType(grantType, client);

  if (logger.isDebugEnabled()) {
   logger.debug("Getting access token for: " + clientId);
  }

        //获取token
  return getAccessToken(client, tokenRequest);

 }
    
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
  return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
 }
 protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
  OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
  return new OAuth2Authentication(storedOAuth2Request, null);
 }    

AuthorizationCodeTokenGranter

AuthorizationCodeTokenGranter是最复杂的。

@Override
 protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

  Map<String, String> parameters = tokenRequest.getRequestParameters();
  String authorizationCode = parameters.get("code");
  String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);

  if (authorizationCode == null) {
   throw new InvalidRequestException("An authorization code must be supplied.");
  }
        //通过code获取OAuth2Authentication ,
  OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
  if (storedAuth == null) {
   throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
  }
        //验证clientId和redirect_uri
  .........省略
        
        // 创建一个全新的 OAuth2Request,并从OAuth2Authentication 中获取到 Authentication 对象
  Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request
    .getRequestParameters());
  combinedParameters.putAll(parameters);
  
  OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);
  
  Authentication userAuth = storedAuth.getUserAuthentication();
  // 创建一个全新的 OAuth2Authentication 对象
  return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);

 }

CompositeTokenGranter

组合多个Granter,返回第一个token。

 public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
  for (TokenGranter granter : tokenGranters) {
   OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
   if (grant!=null) {
    return grant;
   }
  }
  return null;
 }

AuthorizationServerTokenServices

AuthorizationServerTokenServices,token生成与刷新。

public interface AuthorizationServerTokenServices {

 OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;

 OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
   throws AuthenticationException;

 OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}

DefaultTokenServices

AuthorizationServerTokenServices的默认实现类。

@Transactional
 public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
     // 1、 通过 tokenStore 获取到之前存在的token 并判断是否为空、过期,不为空且未过期则直接返回原有存在的token (由于我们常用Jwt 所以这里是 JwtTokenStore ,且 existingAccessToken 永远为空,即每次请求获取token的值均不同,这与RedisTokenStore 是有区别的)
     
  OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
  OAuth2RefreshToken refreshToken = null;
  if (existingAccessToken != null) {
   if (existingAccessToken.isExpired()) {
    if (existingAccessToken.getRefreshToken() != null) {
     refreshToken = existingAccessToken.getRefreshToken();
     tokenStore.removeRefreshToken(refreshToken);
    }
    tokenStore.removeAccessToken(existingAccessToken);
   }
   else {
    tokenStore.storeAccessToken(existingAccessToken, authentication);
    return existingAccessToken;
   }
  }
     // 2、 调用 createRefreshToken 方法生成 refreshToken
  if (refreshToken == null) {
   refreshToken = createRefreshToken(authentication);
  }else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
   ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
   if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
    refreshToken = createRefreshToken(authentication);
   }
  }
     
     // 3、 调用  createAccessToken(authentication, refreshToken) 方法获取 token
  OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
  tokenStore.storeAccessToken(accessToken, authentication);
  // 4、 重新覆盖原有的刷新token(原有的 refreshToken 为UUID 数据,覆盖为 jwtToken)
  refreshToken = accessToken.getRefreshToken();
  if (refreshToken != null) {
   tokenStore.storeRefreshToken(refreshToken, authentication);
  }
  return accessToken;

 }
 

createAccessToken

真正产生token的地方,默认token为uuid。如果需要增加可以使用TokenEnhancerTokenEnhancerChain实现类 )。

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
     // 1、 通过 UUID 创建  DefaultOAuth2AccessToken  并设置上有效时长等信息
  DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
  int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
  if (validitySeconds > 0) {
   token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
  }
  token.setRefreshToken(refreshToken);
  token.setScope(authentication.getOAuth2Request().getScope());
     // 2、 判断 是否存在 token增强器 accessTokenEnhancer ,存在则调用增强器增强方法
  return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
 }

配置增强器

     /**
      * 自定义token扩展链
      *
      * @return tokenEnhancerChain
      */
     @Bean
     public TokenEnhancerChain tokenEnhancerChain() {
         TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
         tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new JwtTokenEnhance(), jwtAccessTokenConverter()));
         return tokenEnhancerChain;
     }

TokenStore

token存储。提供读取,写入,删除token等操作。

AuthorizationServerSecurityConfiguration

此类继承WebSecurityConfigurerAdapter,用于配置Security。并且order设置为0,最先配置。

@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
@Deprecated
@Order(0)
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

 @Autowired
 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

 @Autowired
 private ClientDetailsService clientDetailsService;

 @Autowired
 private AuthorizationServerEndpointsConfiguration endpoints;

引入ClientDetailsServiceConfigurationAuthorizationServerEndpointsConfiguration

HttpSecurity

protected void configure(HttpSecurity http) throws Exception {
  AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
  FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
  http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
  configure(configurer);
  http.apply(configurer);
  String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
  String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
  String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
  if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
   UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
   endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
  }
  // @formatter:off
  http
         .authorizeRequests()
             .antMatchers(tokenEndpointPath).fullyAuthenticated()
             .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
             .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
        .and()
         .requestMatchers()
             .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
        .and()
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
  // @formatter:on
  http.setSharedObject(ClientDetailsService.class, clientDetailsService);
 }

ClientDetailsServiceConfiguration

用于配置ClientDetailsService Bean。

public class ClientDetailsServiceConfiguration {

 @SuppressWarnings("rawtypes")
 private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
 
 @Bean
 public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
  return configurer;
 }

 @Bean
 @Lazy
 @Scope(proxyMode=ScopedProxyMode.INTERFACES)
 public ClientDetailsService clientDetailsService() throws Exception {
  return configurer.and().build();
 }

ClientDetailsService 定义一个方法loadClientByClientId,通过clientId获取Client信息。有2个具体实现:InMemoryClientDetailsServiceJdbcClientDetailsService

public interface ClientDetailsService {

  ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;

}
public interface ClientDetails extends Serializable {
 String getClientId();

 //可以访问的资源列表
 Set<String> getResourceIds();
    //是否需要秘钥
 boolean isSecretRequired();
 //秘钥
 String getClientSecret();

 //是否有权限范围
 boolean isScoped();

 //权限范围
 Set<String> getScope();

 //授权类型
 Set<String> getAuthorizedGrantTypes();

 //返回uri,可以用于判断client
 Set<String> getRegisteredRedirectUri();

 Collection<GrantedAuthority> getAuthorities();

 Integer getAccessTokenValiditySeconds();
 Integer getRefreshTokenValiditySeconds();
 boolean isAutoApprove(String scope);
    //附加信息
 Map<String, Object> getAdditionalInformation();

}

AuthorizationServerEndpointsConfiguration

见上面。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP