当前位置:   article > 正文

OAuth2自动配置源码解读,与类相互之间关系_oauth2autoconfiguration

oauth2autoconfiguration

OAuth2

OAuth2AutoConfiguration(OAuth2自动配置类)

该类是Spring Boot扫描所有jra目录下META-INFO中spring.factories,完成自动加载。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration
  • 1
  • 2

当前类功能:通过@Import完成安全配置
1.OAuth2AuthorizationServerConfiguration,该类需要启动注解@EnableAuthorizationServer才能完成自动引入。
2.OAuth2MethodSecurityConfiguration,该类需要Bean存在GlobalMethodSecurityConfiguration才能完成自动引入。
3.OAuth2MethodSecurityConfiguration,该类

@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class, WebMvcConfigurer.class })
@Import({ OAuth2AuthorizationServerConfiguration.class,
      OAuth2MethodSecurityConfiguration.class, OAuth2ResourceServerConfiguration.class,
      OAuth2RestOperationsConfiguration.class })
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
public class OAuth2AutoConfiguration {

   private final OAuth2ClientProperties credentials;

   public OAuth2AutoConfiguration(OAuth2ClientProperties credentials) {
      this.credentials = credentials;
   }

   @Bean
   public ResourceServerProperties resourceServerProperties() {
      return new ResourceServerProperties(this.credentials.getClientId(),
            this.credentials.getClientSecret());
   }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Configure(授权服务器配置)

@EnableAuthorizationServer

开启注解@EnableAuthorizationServer且继承AuthorizationServerConfigurerAdapter,需要实现AuthorizationServerConfigurerAdapter中的方法。如若不知如何编写可参考OAuth2AuthorizationServerConfiguration中的写法。
@EnableAuthorizationServer

会加载AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

OAuth2AuthorizationServerConfiguration(OAuth2认证服务配置)

启动当前类的条件:
1.@ConditionalOnClass(EnableAuthorizationServer.class) 需要在代码中开启@EnableAuthorizationServer注解。
2.@ConditionOnMissingBean(AuthorizationServerConfigurer.class),需要Bean中不存在AuthorizationServerConfigurer。而AuthorizationServerConfigurerAdapter是实现了AuthorizationServerConfigurer。所以只要在代码中实现了AuthorizationServerConfigurerAdapter就不会加载当前类。
3.@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class),需要Bean中存在AuthorizationServerEndpointsConfiguration类,
4.@EnableConfigurationProperties(AuthorizationServerProperties.class) ,加载配置类信息到AuthorizationServerProperties类中。
5.@Import(AuthorizationServerTokenServicesConfiguration.class),引入AuthorizationServerTokenServicesConfiguration类。

@Configuration
@ConditionalOnClass(EnableAuthorizationServer.class)
@ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
@EnableConfigurationProperties(AuthorizationServerProperties.class)
@Import(AuthorizationServerTokenServicesConfiguration.class)
public class OAuth2AuthorizationServerConfiguration
      extends AuthorizationServerConfigurerAdapter {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
配置ClientDetailsServiceConfigurer

配置客户端详情信息:clientId(客户端id)、clientSecret(客户端秘钥)、resourceIds(资源id)、authorizedGrantTypes(授权类型)、authorities(权限)、scopes(作用域)等信息。

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients
         .inMemory().withClient(this.details.getClientId());
   builder.secret(this.details.getClientSecret())
         .resourceIds(this.details.getResourceIds().toArray(new String[0]))
         .authorizedGrantTypes(
               this.details.getAuthorizedGrantTypes().toArray(new String[0]))
         .authorities(
               AuthorityUtils.authorityListToSet(this.details.getAuthorities())
                     .toArray(new String[0]))
         .scopes(this.details.getScope().toArray(new String[0]));

   if (this.details.getAutoApproveScopes() != null) {
      builder.autoApprove(
            this.details.getAutoApproveScopes().toArray(new String[0]));
   }
   if (this.details.getAccessTokenValiditySeconds() != null) {
      builder.accessTokenValiditySeconds(
            this.details.getAccessTokenValiditySeconds());
   }
   if (this.details.getRefreshTokenValiditySeconds() != null) {
      builder.refreshTokenValiditySeconds(
            this.details.getRefreshTokenValiditySeconds());
   }
   if (this.details.getRegisteredRedirectUri() != null) {
      builder.redirectUris(
            this.details.getRegisteredRedirectUri().toArray(new String[0]));
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
配置AuthorizationServerEndpointsConfigurer

配置认证服务器站点配置
配置AccessTokenConverter(访问Token转换器)
配置TokenStore(Token存储)
如果配置了密码模式授权,则需要配置AuthenticationManager,用于在ResourceOwnerPasswordTokenGranter(密码授权)时候完成用户校验。

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
      throws Exception {
   if (this.tokenConverter != null) {
      endpoints.accessTokenConverter(this.tokenConverter);
   }
   if (this.tokenStore != null) {
      endpoints.tokenStore(this.tokenStore);
   }
   if (this.details.getAuthorizedGrantTypes().contains("password")) {
      endpoints.authenticationManager(this.authenticationManager);
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

AuthorizationServerConfigurerAdapter(认证服务器配置适配器)

详情参考OAuth2AuthorizationServerConfiguration中的配置

AuthorizationServerConfigurerAdapter
/**
* 配置认证服务安全, 通过哪些途径到/oauth/token站点. 
* /oauth/authorize站点也是需要保护的, 这是一个面向用户的普通站点,在UI方面需要提供相似的安全服务。 具体详见AuthorizationServerSecurityConfigurer类。
*/
void configure(AuthorizationServerSecurityConfigurer security) throws Exception;

/**
* 配置ClientDetailsService。例如声明单个Clients及其属性,请注意除非在AuthorizationServerEndpointsConfigurer中配置AuthenticationManger。否则不会启用密码授予(即使某些客户端允许这样做)。至少声明一个客户端或完全格式的自定义ClientDetailsService,否则服务器将不会启动。*/
void configure(ClientDetailsServiceConfigurer clients) throws Exception;

/**
*授权服务器配置非安全特性,如令牌存储、令牌自定义、用户批准和授权类型。默认情况下,您不需要做任何事情,除非您需要密码授予,在这种情况下,需要提供AuthenticationManager。
*/
void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

AuthorizationServerEndpointsConfiguration(认证服务器站点配置)

创建AuthorizationServerEndpointsConfigurer,并注入ClientDetailsService

AuthorizationEndpoint(授权站点)

初始化:配置授权页面/oauth/confirm_access。异常处理器、异常页面、ClientDetailsService、AuthorizationCodeServices(授权码服务器)、OAuth2请求工厂、OAuth2请求校验、用户授权处理器、回调地址解析器、token授权者。

@Bean
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
TokenEndpoint(Token站点)

在到达Token站点之前,已经通过ClientCredentialsTokenEndpointFilter完成客户端认证。
这里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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
CheckTokenEndpoint(校验Token站点)
@Bean
public CheckTokenEndpoint checkTokenEndpoint() {
   CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
   endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
   endpoint.setExceptionTranslator(exceptionTranslator());
   return endpoint;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
WhitelabelApprovalEndpoint(白名单审批站点)
@Bean
public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() {
   return new WhitelabelApprovalEndpoint();
}
  • 1
  • 2
  • 3
  • 4
WhitelabelErrorEndpoint(白名单异常站点)
@Bean
public WhitelabelErrorEndpoint whitelabelErrorEndpoint() {
   return new WhitelabelErrorEndpoint();
}
  • 1
  • 2
  • 3
  • 4
TokenKeyEndpoint(Token密码站点)
@Component
protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor {

   private BeanDefinitionRegistry registry;

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory,
            JwtAccessTokenConverter.class, false, false);
      if (names.length > 0) {
         BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.class);
         builder.addConstructorArgReference(names[0]);
         registry.registerBeanDefinition(TokenKeyEndpoint.class.getName(), builder.getBeanDefinition());
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

AuthorizationServerEndpointsConfigurer(认证服务站点配置类)

这个类可以在AuthorizationServerConfigurerAdapter中进行相应的修改。

AuthorizationServerTokenServices(认证服务器Token服务)

当如果未设置AuthorizationServerTokenServices,则采用默认的DefaultTokenService服务,
并设置ClientDetailsService的认证管理器为PreAuthenticatedAuthenticationProvider。来完成客户端认证。

public AuthorizationServerTokenServices getTokenServices() {
   return ProxyCreator.getProxy(AuthorizationServerTokenServices.class,
         new ObjectFactory<AuthorizationServerTokenServices>() {
            @Override
            public AuthorizationServerTokenServices getObject() throws BeansException {
               return tokenServices();
            }
         });
}
private AuthorizationServerTokenServices tokenServices() {
   if (tokenServices != null) {
      return tokenServices;
   }
   this.tokenServices = createDefaultTokenServices();
   return tokenServices;
}

private DefaultTokenServices createDefaultTokenServices() {
   DefaultTokenServices tokenServices = new DefaultTokenServices();
   tokenServices.setTokenStore(tokenStore());
   tokenServices.setSupportRefreshToken(true);
   tokenServices.setReuseRefreshToken(reuseRefreshToken);
   tokenServices.setClientDetailsService(clientDetailsService());
   tokenServices.setTokenEnhancer(tokenEnhancer());
   addUserDetailsService(tokenServices, this.userDetailsService);
   return tokenServices;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
   if (userDetailsService != null) {
      PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
      provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
            userDetailsService));
      tokenServices
            .setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
TokenGranter(Token授权)

当tokenGranter为空,则将CompositeTokenGranter类代理getDefaultTokenGranters方法创建的5种TokenGranterAuthorizationCodeTokenGranterRefreshTokenGranterImplicitTokenGranterClientCredentialsTokenGranterResourceOwnerPasswordTokenGranter

public TokenGranter getTokenGranter() {
   return tokenGranter();
}
private TokenGranter tokenGranter() {
   if (tokenGranter == null) {
      tokenGranter = new TokenGranter() {
         private CompositeTokenGranter delegate;

         @Override
         public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
            if (delegate == null) {
               delegate = new CompositeTokenGranter(getDefaultTokenGranters());
            }
            return delegate.grant(grantType, tokenRequest);
         }
      };
   }
   return tokenGranter;
}
private List<TokenGranter> getDefaultTokenGranters() {
   ClientDetailsService clientDetails = clientDetailsService();
   AuthorizationServerTokenServices tokenServices = tokenServices();
   AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
   OAuth2RequestFactory requestFactory = requestFactory();

   List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
   tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
         requestFactory));
   tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
   ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
   tokenGranters.add(implicit);
   tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
   if (authenticationManager != null) {
      tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
            clientDetails, requestFactory));
   }
   return tokenGranters;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
WebResponseExceptionTranslator(Web返回异常翻译器)

默认返回的是DefaultWebResponseExceptionTranslator。

public WebResponseExceptionTranslator<OAuth2Exception> getExceptionTranslator() {
   return exceptionTranslator();
}
private WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator() {
   if (exceptionTranslator != null) {
      return exceptionTranslator;
   }
   exceptionTranslator = new DefaultWebResponseExceptionTranslator();
   return exceptionTranslator;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
ResourceServerTokenServices(资源服务器Token服务)

如果未空则采用默认的ResourceServerTokenServices,由方法createDefaultTokenServices创建详情见AuthorizationServerTokenServices创建。

public ResourceServerTokenServices getResourceServerTokenServices() {
   return resourceTokenServices();
}
private ResourceServerTokenServices resourceTokenServices() {
   if (resourceTokenServices == null) {
      if (tokenServices instanceof ResourceServerTokenServices) {
         return (ResourceServerTokenServices) tokenServices;
      }
      resourceTokenServices = createDefaultTokenServices();
   }
   return resourceTokenServices;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
TokenStore(Token存储)

如果未设置TokenStore,则判断AccessTokenConverter(访问Token转换器)是否是JwtAccessTokenConverter。如果是则创建JwtTokenStore(JwtToekn存储),否则则采用InMemoryTokenStore(内存Token存储)

public TokenStore getTokenStore() {
   return tokenStore();
}
private TokenStore tokenStore() {
   if (tokenStore == null) {
      if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
         this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
      }
      else {
         this.tokenStore = new InMemoryTokenStore();
      }
   }
   return this.tokenStore;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
AccessTokenConverter(访问Token转换器)

如果未设置,则创建DefaultAccessTokenConverter(默认访问转换器)

private AccessTokenConverter accessTokenConverter() {
   if (this.accessTokenConverter == null) {
      accessTokenConverter = new DefaultAccessTokenConverter();
   }
   return this.accessTokenConverter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
AuthenticationManager(认证管理,设置)

用于在TokenGranter密码时候需要使用

public AuthorizationServerEndpointsConfigurer authenticationManager(AuthenticationManager authenticationManager) {
   this.authenticationManager = authenticationManager;
   return this;
}
  • 1
  • 2
  • 3
  • 4

AuthorizationServerSecurityConfiguration(认证服务器安全配置)

HttpSecurity(初始化)

主要完成BasicAuthenticationFilter的初始化,主要领域是/oauth/client。其中主要是check_token时候需要根据Basic 完成客户端认证, 才能请求到/oauth/check_token方法。

@Override
public void init(HttpSecurity http) throws Exception {

   registerDefaultAuthenticationEntryPoint(http);
   if (passwordEncoder != null) {
      ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
      clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
      http.getSharedObject(AuthenticationManagerBuilder.class)
            .userDetailsService(clientDetailsUserDetailsService)
            .passwordEncoder(passwordEncoder());
   }
   else {
      http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
   }
   http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
         .httpBasic().realmName(realm);
   if (sslOnly) {
      http.requiresChannel().anyRequest().requiresSecure();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
HttpSecurity(配置)

创建AuthorizationServerSecurityConfigurer
1.这里完成站点权限的配置:/oauth/token_key、/oauth/check_token。
2.http中设置ClientDetailsService为clientDetailsService方便,后续使用。
3.

@Override
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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

AuthorizationServerSecurityConfigurer(认证服务器安全配置类)

该类继承了SecurityConfigurerAdapter,并可在AuthorizationServerConfigurerAdapter类中修改

HttpSecurity(配置)

由clientCredentialsTokenEndpointFilter完成ClientCredentialsTokenEndpointFilter过滤链的创建。

@Override
public void configure(HttpSecurity http) throws Exception {
   
   // ensure this is initialized
   frameworkEndpointHandlerMapping();
   if (allowFormAuthenticationForClients) {
      clientCredentialsTokenEndpointFilter(http);
   }

   for (Filter filter : tokenEndpointAuthenticationFilters) {
      http.addFilterBefore(filter, BasicAuthenticationFilter.class);
   }

   http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
HttpSecurityHttp(初始化)

1.主要是设置UserDetailsService为ClientDetailsUserDetailsService
2.完成BasicAuthenticationFilter的配置,用于在/oauth/token之外的的接口中验证客户端信息

@Override
public void init(HttpSecurity http) throws Exception {

   registerDefaultAuthenticationEntryPoint(http);
   if (passwordEncoder != null) {
      ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
      clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
      http.getSharedObject(AuthenticationManagerBuilder.class)
            .userDetailsService(clientDetailsUserDetailsService)
            .passwordEncoder(passwordEncoder());
   }
   else {
      http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
   }
   http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
         .httpBasic().realmName(realm);
   if (sslOnly) {
      http.requiresChannel().anyRequest().requiresSecure();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
ClientCredentialsTokenEndpointFilter(客户端资质Token站点过滤器)

初始化ClientCredentialsTokenEndpointFilter 并设置过滤地址/oauth/token
设置AuthenticationManager为客户端信息完成认证
设置UserDetailsService为ClientDetailsUserDetailsService

private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
   ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
         frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
   clientCredentialsTokenEndpointFilter
         .setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
   OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
   authenticationEntryPoint.setTypeName("Form");
   authenticationEntryPoint.setRealmName(realm);
   clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
   clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
   http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
   return clientCredentialsTokenEndpointFilter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Endpoint(站点)

站点的创建主要由AuthorizationServerEndpointsConfiguration创建。

AbstractEndpoint

AuthorizationEndpoint(授权站点)

用于第三方授权的端点,

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

   AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

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

   if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
      throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
   }

   if (authorizationRequest.getClientId() == null) {
      throw new InvalidClientException("A client id must be provided");
   }

   try {

      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());

      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);

  
      oauth2RequestValidator.validateScope(authorizationRequest, client);

     authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
            (Authentication) principal);
      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;
   }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
TokenEndpoint(Token站点)

经过ClientCredentialsTokenEndpointFilter过滤器完成客户端认证,在进入到当前/oauth/token接口。
1.判断是否已通过客户端认证。未认证则直接抛出异常。
2.获取clientId,在根据clientId从ClientDetaisService中获取ClientDeatias。
3.请求参数和ClientDeatias通过OAuth2RequestFactory转换为当前方法需要的TokenRequest。
4.如果ClientDetails不为空通过OAuth2RequestValidator完成请求验证,确保输入参数正确。
5.当前类不支持implicit模式授权
6.在通过TokenGranter中的代理完成,授权类型匹配,进行相应的授权。

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

   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 = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

   if (clientId != null && !clientId.equals("")) {
      // Only validate the client details if a client authenticated during this
      // request.
      if (!clientId.equals(tokenRequest.getClientId())) {
         // double check to make sure that the client ID in the token request is the same as that in the
         // authenticated client
         throw new InvalidClientException("Given client ID does not match authenticated client");
      }
   }
   if (authenticatedClient != null) {
      oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
   }
   if (!StringUtils.hasText(tokenRequest.getGrantType())) {
      throw new InvalidRequestException("Missing grant type");
   }
   if (tokenRequest.getGrantType().equals("implicit")) {
      throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
   }

   if (isAuthCodeRequest(parameters)) {
      // The scope was requested or determined during the authorization step
      if (!tokenRequest.getScope().isEmpty()) {
         logger.debug("Clearing scope of incoming token request");
         tokenRequest.setScope(Collections.<String> emptySet());
      }
   }

   if (isRefreshTokenRequest(parameters)) {
      // A refresh token has its own default scopes, so we should ignore any added by the factory here.
      tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
   }

   OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
   if (token == null) {
      throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
   }

   return getResponse(token);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
TokenKeyEndpoint(Token加密站点)

获取到加密的方式,默认是请求无权限的,需要再AuthorizationServerConfigurerAdapter中配置为允许所有请求访问
通过JwtAccessTokenConverter返回需要的加密信息。

@RequestMapping(value = "/oauth/token_key", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> getKey(Principal principal) {
   if ((principal == null || principal instanceof AnonymousAuthenticationToken) && !converter.isPublic()) {
       throw new AccessDeniedException("You need to authenticate to see a shared key");
   }
   Map<String, String> result = converter.getKey();
   return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

WhitelabelApprovalEndpoint

在授权码类型授权时候,用户权限授权的页面。

@RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
   final String approvalContent = createTemplate(model, request);
   if (request.getAttribute("_csrf") != null) {
      model.put("_csrf", request.getAttribute("_csrf"));
   }
   View approvalView = new View() {
      @Override
      public String getContentType() {
         return "text/html";
      }

      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
         response.setContentType(getContentType());
         response.getWriter().append(approvalContent);
      }
   };
   return new ModelAndView(approvalView, model);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

WhitelabelErrorEndpoint

授权异常页面:在授权码类型中

@RequestMapping("/oauth/error")
public ModelAndView handleError(HttpServletRequest request) {
   Map<String, Object> model = new HashMap<String, Object>();
   Object error = request.getAttribute("error");
   String errorSummary;
   if (error instanceof OAuth2Exception) {
      OAuth2Exception oauthError = (OAuth2Exception) error;
      errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());
   }
   else {
      errorSummary = "Unknown error";
   }
   final String errorContent = ERROR.replace("%errorSummary%", errorSummary);
   View errorView = new View() {
      @Override
      public String getContentType() {
         return "text/html";
      }

      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
         response.setContentType(getContentType());
         response.getWriter().append(errorContent);
      }
   };
   return new ModelAndView(errorView, model);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

TokenGranter(Token授权)

该类的创建在AuthorizationServerEndpointsConfigurertokenGranter方法中。

CompositeTokenGranter

通过grantType(授权类型)判断当前支持,不支持则循环下一个。

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
   for (TokenGranter granter : tokenGranters) {
      OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
      if (grant!=null) {
         return grant;
      }
   }
   return null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

AbstractTokenGranter(抽象Token授权)

grant方法
1.判断grantType是否和当前类的grantType(授权类型)相等。不相等则返回null。
2.根据clientId从ClientDetailsService获取ClientDetails(客户端详情)。
3.从客户端详情中,校验是否支持当前grantType。不支持则抛出异常“Unauthorization grant type ”。
4.通过getAccessToken方法返回OAuth2AccessToken。

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

   if (!this.grantType.equals(grantType)) {
      return null;
   }
   
   String clientId = tokenRequest.getClientId();
   ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
   validateGrantType(grantType, client);

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

   return getAccessToken(client, tokenRequest);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

getAccessToken
根据

protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
   return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
  • 1
  • 2
  • 3

getOAuth2Authentication
根据客户端信息和TokenReques,获取到OAuth2Authentication。
下面很多类都是重写当前方法

protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
   OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
   return new OAuth2Authentication(storedOAuth2Request, null);
}
  • 1
  • 2
  • 3
  • 4
ResourceOwnerPasswordTokenGranter(密码Token授权)

支持的授权类型password

private static final String GRANT_TYPE = "password";
  • 1

通过ClientCredentialsTokenEndpointFilter认证客户端信息,在到/oauth/token中通过代理CompositeTokenGranter代理,匹配对应授权类型,到达OAuth2Authentication。
1.获取参数中的username 和 password,创建UsernamePasswordAuthenticationToken。
2.通过AuthorizationServerConfigurerAdapter中的配置方AuthorizationServerEndpointsConfigurer配置了AuthenticationManger(认证管理)。完成用户认证。
3.认证完成之后通过OAuth2RequestFactory创建OAuth2Authentication。

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

   Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
   String username = parameters.get("username");
   String password = parameters.get("password");
   // Protect from downstream leaks of password
   parameters.remove("password");

   Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
   ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
   try {
      userAuth = authenticationManager.authenticate(userAuth);
   }
   catch (AccountStatusException ase) {
      //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
      throw new InvalidGrantException(ase.getMessage());
   }
   catch (BadCredentialsException e) {
      // If the username/password are wrong the spec says we should send 400/invalid grant
      throw new InvalidGrantException(e.getMessage());
   }
   if (userAuth == null || !userAuth.isAuthenticated()) {
      throw new InvalidGrantException("Could not authenticate user: " + username);
   }
   
   OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);    
   return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
RefreshTokenGranter(刷新Token授权)

支持授权类型refresh_token

private static final String GRANT_TYPE = "refresh_token";
  • 1

从参数refersh_token中获取到值,在从TokenServices中刷新OAuth2AccessToken

@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
   String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
   return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
  • 1
  • 2
  • 3
  • 4
  • 5
AuthorizationCodeTokenGranter(授权码Token授权)

支持授权类型:authorization_code

private static final String GRANT_TYPE = "authorization_code";
  • 1

1.获取通过/oatuh/authorize获取的的code,请求回调地址。
2.通过code从AuthorizationCodeServices中获取OAuth2Authentication认证信息。
3.判断回调地址和获取的认证信息中的回调地址是否相等,如果不相等则抛出“Redirect URI mismatch”(回调地址不匹配)
4.将OAuth2Authentication的信息重新组装为OAuth2Authentication

@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.");
   }

   OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
   if (storedAuth == null) {
      throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
   }

   OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();
   // https://jira.springsource.org/browse/SECOAUTH-333
   // This might be null, if the authorization was done without the redirect_uri parameter
   String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get(
         OAuth2Utils.REDIRECT_URI);

   if ((redirectUri != null || redirectUriApprovalParameter != null)
         && !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {
      throw new RedirectMismatchException("Redirect URI mismatch.");
   }

   String pendingClientId = pendingOAuth2Request.getClientId();
   String clientId = tokenRequest.getClientId();
   if (clientId != null && !clientId.equals(pendingClientId)) {
      // just a sanity check.
      throw new InvalidClientException("Client ID mismatch");
   }

   // Secret is not required in the authorization request, so it won't be available
   // in the pendingAuthorizationRequest. We do want to check that a secret is provided
   // in the token request, but that happens elsewhere.

   Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request
         .getRequestParameters());
   // Combine the parameters adding the new ones last so they override if there are any clashes
   combinedParameters.putAll(parameters);
   
   // Make a new stored request with the combined parameters
   OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);
   
   Authentication userAuth = storedAuth.getUserAuthentication();
   
   return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
ImplicitTokenGranter(隐式Token授权)

支持授权类型:implicit

private static final String GRANT_TYPE = "implicit";
  • 1

从上下文中中获取Authentication(认证信息)
判断认证信息是否为空,为空则抛出异常
否则创建OAuth2Authentication,并返回

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

   Authentication userAuth = SecurityContextHolder.getContext().getAuthentication();
   if (userAuth==null || !userAuth.isAuthenticated()) {
      throw new InsufficientAuthenticationException("There is no currently logged in user");
   }
   Assert.state(clientToken instanceof ImplicitTokenRequest, "An ImplicitTokenRequest is required here. Caller needs to wrap the TokenRequest.");
   
   OAuth2Request requestForStorage = ((ImplicitTokenRequest)clientToken).getOAuth2Request();
   
   return new OAuth2Authentication(requestForStorage, userAuth);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
ClientCredentialsTokenGranter(客户端认证Token授权)

支持授权码类型:client_credentials

private static final String GRANT_TYPE = "client_credentials";
  • 1

通过抽象类的grant方法,返回OAuth2AccessToken。
在讲OAuth2AccessToken通过DefaultOAuth2AccessToken创建,并返回。

@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
   OAuth2AccessToken token = super.grant(grantType, tokenRequest);
   if (token != null) {
      DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token);
      // The spec says that client credentials should not be allowed to get a refresh token
      if (!allowRefresh) {
         norefresh.setRefreshToken(null);
      }
      token = norefresh;
   }
   return token;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Configure(资源服务器配置)

开启注解@EnableResourceServer且继承ResourceServerConfigurerAdapter,需要实现ResourceServerConfigurerAdapter中的方法。如若不知如何编写可参考ResourceServerConfigurer中的写法。

EnableResourceServer

OAuth2ResourceServerConfiguration
该类由OAuth2AutoConfiguration通过@Import引入

ResourceServerConfigurerAdapter

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}

@Override
public void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests().anyRequest().authenticated();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ResourceServerConfiguration

继承了WebSecurityConfigurerAdapter,实现了资源安全的配置
1.创建ResourceServerSecurityConfigurer
2.创建ResourceServerTokenServices

protected void configure(HttpSecurity http) throws Exception {
   ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
   ResourceServerTokenServices services = resolveTokenServices();
   if (services != null) {
      resources.tokenServices(services);
   }
   else {
      if (tokenStore != null) {
         resources.tokenStore(tokenStore);
      }
      else if (endpoints != null) {
         resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
      }
   }
   if (eventPublisher != null) {
      resources.eventPublisher(eventPublisher);
   }
   for (ResourceServerConfigurer configurer : configurers) {
      configurer.configure(resources);
   }
   // @formatter:off
   http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
   // N.B. exceptionHandling is duplicated in resources.configure() so that
   // it works
   .exceptionHandling()
         .accessDeniedHandler(resources.getAccessDeniedHandler()).and()
         .sessionManagement()
         .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
         .csrf().disable();
   // @formatter:on
   http.apply(resources);
   if (endpoints != null) {
      // Assume we are in an Authorization Server
      http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
   }
   for (ResourceServerConfigurer configurer : configurers) {
      // Delegates can add authorizeRequests() here
      configurer.configure(http);
   }
   if (configurers.isEmpty()) {
      // Add anyRequest() last as a fall back. Spring Security would
      // replace an existing anyRequest() matcher with this one, so to
      // avoid that we only add it if the user hasn't configured anything.
      http.authorizeRequests().anyRequest().authenticated();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

ResourceServerSecurityConfigurer

继承ServerSecurityConfigurerAdapter,

HttpSecurity(配置)

配置了AuthenticationManagerOAuth2AuthenticationProcessingFilter(OAuth2认证处理过滤器)。
OAuth2AuthenticationProcessingFilter通过AuthenticationManager完成身份认证。

@Override
public void configure(HttpSecurity http) throws Exception {

   AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
   resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
   resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
   resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
   if (eventPublisher != null) {
      resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
   }
   if (tokenExtractor != null) {
      resourcesServerFilter.setTokenExtractor(tokenExtractor);
   }
   if (authenticationDetailsSource != null) {
      resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
   }
   resourcesServerFilter = postProcess(resourcesServerFilter);
   resourcesServerFilter.setStateless(stateless);

   http
      .authorizeRequests().expressionHandler(expressionHandler)
   .and()
      .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
      .exceptionHandling()
         .accessDeniedHandler(accessDeniedHandler)
         .authenticationEntryPoint(authenticationEntryPoint);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
AuthenticationManager(身份认证管理)

创建OAuth2AuthenticationManager 为身份认证管理器。
1.设置资源id
2.设置ResourceServerTokenService,完成对应Token的认证并返回认证结果。
3.设置ClientDetailsService,验证返回的认证信息和当前配置的ClientDetails的信息是否一致。

private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) {
   OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
   if (authenticationManager != null) {
      if (authenticationManager instanceof OAuth2AuthenticationManager) {
         oauthAuthenticationManager = (OAuth2AuthenticationManager) authenticationManager;
      }
      else {
         return authenticationManager;
      }
   }
   oauthAuthenticationManager.setResourceId(resourceId);
   oauthAuthenticationManager.setTokenServices(resourceTokenServices(http));
   oauthAuthenticationManager.setClientDetailsService(clientDetails());
   return oauthAuthenticationManager;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
ResouceServerTokenServices(资源服务器Token服务器)

如果未设置ResourceServerTokenServices,则创建DefaultTokenServices(默认Token服务器)来完成用户校验。

private ResourceServerTokenServices resourceTokenServices(HttpSecurity http) {
   tokenServices(http);
   return this.resourceTokenServices;
}

private ResourceServerTokenServices tokenServices(HttpSecurity http) {
   if (resourceTokenServices != null) {
      return resourceTokenServices;
   }
   DefaultTokenServices tokenServices = new DefaultTokenServices();
   tokenServices.setTokenStore(tokenStore());
   tokenServices.setSupportRefreshToken(true);
   tokenServices.setClientDetailsService(clientDetails());
   this.resourceTokenServices = tokenServices;
   return tokenServices;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

ResourceServerTokenServices(资源服务器Token服务)

RemoteTokenServices(远程Token服务)

1.设置请求参数Token的值为 accessToken(访问token)
2.在请求头Authorization中设置 Basic请求头以及clientId和clientSecret的信息。用于通过BasicAuthenticationFilter完成身份认证,在通过/oauth/check_token接口校验Token。
3.发起请求,并返回结果。
4.如果返回结果中存在error值则抛出无线访问Token异常。
5.如果返回结果中存在active为fasle,则抛出无效访问Token异常。
6.通过DefaultAccessTokenConverter完成结果转换为OAuth2Authentication

@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

   MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
   formData.add(tokenName, accessToken);
   HttpHeaders headers = new HttpHeaders();
   headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
   Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);

   if (map.containsKey("error")) {
      if (logger.isDebugEnabled()) {
         logger.debug("check_token returned error: " + map.get("error"));
      }
      throw new InvalidTokenException(accessToken);
   }

   // gh-838
   if (!Boolean.TRUE.equals(map.get("active"))) {
      logger.debug("check_token returned active attribute: " + map.get("active"));
      throw new InvalidTokenException(accessToken);
   }

   return tokenConverter.extractAuthentication(map);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Filter(过滤器)

AbstractAuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
   HttpServletRequest request = (HttpServletRequest)req;
   HttpServletResponse response = (HttpServletResponse)res;
   if (!this.requiresAuthentication(request, response)) {
       chain.doFilter(request, response);
   } else {
       if (this.logger.isDebugEnabled()) {
           this.logger.debug("Request is to process authentication");
       }

       Authentication authResult;
       try {
           authResult = this.attemptAuthentication(request, response);
           if (authResult == null) {
               return;
           }

           this.sessionStrategy.onAuthentication(authResult, request, response);
       } catch (InternalAuthenticationServiceException var8) {
           this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
           this.unsuccessfulAuthentication(request, response, var8);
           return;
       } catch (AuthenticationException var9) {
           this.unsuccessfulAuthentication(request, response, var9);
           return;
       }

       if (this.continueChainBeforeSuccessfulAuthentication) {
           chain.doFilter(request, response);
       }

       this.successfulAuthentication(request, response, chain, authResult);
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

ClientCredentialsTokenEndpointFilter(客户端认证Token站点过滤器)

该类创建由AuthorizationServerSecurityConfigurer中的clientCredentialsTokenEndpointFilter方法创建。

1.从请求参数中获取client_id 和 client_secret。
2.从请求上下文中获取当前的Authentication(认证信息),如果存在则直接返回。
3.将获取的clientId和clientSecret转换为UsernamePasswordAuthenticationToken并通过AuthenticationManager完成认证。

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException, IOException, ServletException {

   if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
      throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
   }

   String clientId = request.getParameter("client_id");
   String clientSecret = request.getParameter("client_secret");

   // If the request is already authenticated we can assume that this
   // filter is not needed
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
   if (authentication != null && authentication.isAuthenticated()) {
      return authentication;
   }

   if (clientId == null) {
      throw new BadCredentialsException("No client credentials presented");
   }

   if (clientSecret == null) {
      clientSecret = "";
   }

   clientId = clientId.trim();
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
         clientSecret);

   return this.getAuthenticationManager().authenticate(authRequest);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

OAuth2AuthenticationProcessingFilter(OAuth2认证过程处理器)

ResourceServerSecurityConfigurer中的congigure(HttpSecurity http)中配置。
1.通过TokenExtractor(Token提取器)获取Authentication(认证信息)
2.如果Authentication为空,则通过上下文判断以前是否已认证,如果已认证则清空上下文。
3.通过AuthenticationManager验证Token,并返回Authentication

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 = 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;
            needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
         }
         Authentication authResult = authenticationManager.authenticate(authentication);

         if (debug) {
            logger.debug("Authentication success: " + authResult);
         }

         eventPublisher.publishAuthenticationSuccess(authResult);
         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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

BasicAuthenticationFilter(Basic认证过滤器)

1.通过BasicAuthenticationConverter将请求信息(请求头存在Authorization
且,获取的值以Basic开头 username:password 这种拼接)转换为UsernamePasswordAuthenticationToken 。
2.如果认证信息为空,则过滤链继续执行。
3.根据username从上下文中取Authentication,判断是否已认证,如果已认证则继续执行。
4.如果上下文中没有则通过AuthenticationManger完成认证,并将认证信息保存在上下文中。

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
   boolean debug = this.logger.isDebugEnabled();

   try {
       UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
       if (authRequest == null) {
           chain.doFilter(request, response);
           return;
       }

       String username = authRequest.getName();
       if (debug) {
           this.logger.debug("Basic Authentication Authorization header found for user '" + username + "'");
       }

       if (this.authenticationIsRequired(username)) {
           Authentication authResult = this.authenticationManager.authenticate(authRequest);
           if (debug) {
               this.logger.debug("Authentication success: " + authResult);
           }

           SecurityContextHolder.getContext().setAuthentication(authResult);
           this.rememberMeServices.loginSuccess(request, response, authResult);
           this.onSuccessfulAuthentication(request, response, authResult);
       }
   } catch (AuthenticationException var8) {
       SecurityContextHolder.clearContext();
       if (debug) {
           this.logger.debug("Authentication request for failed!", var8);
       }

       this.rememberMeServices.loginFail(request, response);
       this.onUnsuccessfulAuthentication(request, response, var8);
       if (this.ignoreFailure) {
           chain.doFilter(request, response);
       } else {
           this.authenticationEntryPoint.commence(request, response, var8);
       }

       return;
   }

   chain.doFilter(request, response);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

身份认证(AuthenticationManager)

处理身份认证请求
尝试对传递的身份验证对象进行身份验证,如果成功,则返回一个完全填充的身份验证对象(包括授予的权限)。
AuthenticationManager必须遵守以下关于异常规范:
1.如果帐户被禁用AuthenticationManager需要捕获这个状态并抛出DisabledException。
2.如果帐户被锁定AuthenticationManager需要捕获这个状态并抛出LockedException。
3.如果提供了不正确的凭据,必须抛出BadCredentialsException。虽然上述异常是可选的,但AuthenticationManager必须始终测试凭据。
应该对异常进行捕获,并在适用的情况下按上述顺序抛出异常(如果帐户被禁用或锁定,则身份验证请求立即被拒绝,授权过程不执行)。这可以防止对禁用或锁定的帐户进行授权。

Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
  • 1
  • 2

提供身份认证管理(ProviderManager)

通过系列身份认证提供者完成身份认证。

/** 身份认证事件发布*/
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
/** 系列身份认证提供者*/
private List<AuthenticationProvider> providers = Collections.emptyList();
/** 消息资源访问 */
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

循环身份认证提供者,看当前身份认证提供者是否支持当前的身份认证。
如果支持,则开启认证。

OAuth2AuthenticationManager(OAuth2身份认证管理)

ResourceServerSecurityConfigurer中的oauthAuthenticationManager方法中创建。
1.通过ResourceTokenServices完成Token的验证并返回OAuth2Authentication。
2.如果OAuth2Authentication为空,则抛出“Invalid token”(无效Token)
3.比较当前Token返回的资源id和当前服务配置的资源id是否匹配,则抛出异常“Invalid token does not contain resource id”(无线Token不包含当前资源id)。
4.如果配置了ClientDetailsService则会校验client_id和client_secret等信息。

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

   if (authentication == null) {
      throw new InvalidTokenException("Invalid token (token not found)");
   }
   String token = (String) authentication.getPrincipal();
   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
         details.setDecodedDetails(auth.getDetails());
      }
   }
   auth.setDetails(authentication.getDetails());
   auth.setAuthenticated(true);
   return auth;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/393200
推荐阅读
  

闽ICP备14008679号