OAuth 2 Developers Guide:https://projects.spring.io/spring-security-oauth/docs/oauth2.htmlhtml
The @EnableAuthorizationServer statement is as follows:java
/** * Convenience annotation for enabling an Authorization Server (i.e. an {@link AuthorizationEndpoint} and a * {@link TokenEndpoint}) in the current application context, which must be a {@link DispatcherServlet} context. Many * features of the server can be customized using <code>@Beans</code> of type {@link AuthorizationServerConfigurer} * (e.g. by extending {@link AuthorizationServerConfigurerAdapter}). The user is responsible for securing the * Authorization Endpoint (/oauth/authorize) using normal Spring Security features ({@link EnableWebSecurity * @EnableWebSecurity} etc.), but the Token Endpoint (/oauth/token) will be automatically secured using HTTP Basic * authentication on the client's credentials. Clients <em>must</em> be registered by providing a * {@link ClientDetailsService} through one or more AuthorizationServerConfigurers. * * @author Dave Syer * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer { }
enable authorization Server(AuthorizationEndpoint(受權端點)和TokenEndpoint(令牌端點)),通常設置在extends AuthorizationServerConfigurerAdapter的類。spring
經過一個或多個AuthprizationServerConfigurers提供一個ClientDetailService來註冊客戶端。服務器
該註解同時會啓用AuthorizationEndpoint和TokenEndpoint,而且須要在DispatcherServlet上下文。app
The @AuthorizationServerConfigurer statement is as follows:less
/** * Convenient strategy for configuring an OAUth2 Authorization Server. Beans of this type are applied to the Spring * context automatically if you {@link EnableAuthorizationServer @EnableAuthorizationServer}. * * @author Dave Syer * */ public interface AuthorizationServerConfigurer { /** * Configure the security of the Authorization Server, which means in practical terms the /oauth/token endpoint. The * /oauth/authorize endpoint also needs to be secure, but that is a normal user-facing endpoint and should be * secured the same way as the rest of your UI, so is not covered here. The default settings cover the most common * requirements, following recommendations from the OAuth2 spec, so you don't need to do anything here to get a * basic server up and running. * * @param security a fluent configurer for security features */ void configure(AuthorizationServerSecurityConfigurer security) throws Exception; /** * Configure the {@link ClientDetailsService}, e.g. declaring individual clients and their properties. Note that * password grant is not enabled (even if some clients are allowed it) unless an {@link AuthenticationManager} is * supplied to the {@link #configure(AuthorizationServerEndpointsConfigurer)}. At least one client, or a fully * formed custom {@link ClientDetailsService} must be declared or the server will not start. * * @param clients the client details configurer */ void configure(ClientDetailsServiceConfigurer clients) throws Exception; /** * Configure the non-security features of the Authorization Server endpoints, like token store, token * customizations, user approvals and grant types. You shouldn't need to do anything by default, unless you need * password grants, in which case you need to provide an {@link AuthenticationManager}. * * @param endpoints the endpoints configurer */ void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception; }
服務器會使用AuthorizationServerConfigurer類型的Bean對象。某些功能能夠經過擴展AuthorizationServerConfigurerAdapter implementation。AuthorizationServerConfigurerAdapter類是適配器類,實現了AuthorizationServerConfigurer接口,提供空實現。dom
TheAuthorizationServerConfigurerAdapter statement is as follows:ide
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer { @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { } }
AuthorizationServerConfigurer interface default implementation class:OAuth2AuthorizationServerConfiguration。post
OAuth2AuthprizationServerConfiguration statement is as follows:ui
/** * Configuration for a Spring Security OAuth2 authorization server. Back off if another * {@link AuthorizationServerConfigurer} already exists or if authorization server is not * enabled. * * @author Greg Turnquist * @author Dave Syer * @since 1.3.0 */ @Configuration @ConditionalOnClass(EnableAuthorizationServer.class) @ConditionalOnMissingBean(AuthorizationServerConfigurer.class) @ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class) @EnableConfigurationProperties(AuthorizationServerProperties.class) public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { private static final Log logger = LogFactory .getLog(OAuth2AuthorizationServerConfiguration.class); @Autowired private BaseClientDetails details; @Autowired private AuthenticationManager authenticationManager; @Autowired(required = false) private TokenStore tokenStore; @Autowired private AuthorizationServerProperties properties; @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])); } } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { if (this.tokenStore != null) { endpoints.tokenStore(this.tokenStore); } if (this.details.getAuthorizedGrantTypes().contains("password")) { endpoints.authenticationManager(this.authenticationManager); } } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { if (this.properties.getCheckTokenAccess() != null) { security.checkTokenAccess(this.properties.getCheckTokenAccess()); } if (this.properties.getTokenKeyAccess() != null) { security.tokenKeyAccess(this.properties.getTokenKeyAccess()); } if (this.properties.getRealm() != null) { security.realm(this.properties.getRealm()); } } @Configuration protected static class ClientDetailsLogger { @Autowired private OAuth2ClientProperties credentials; @PostConstruct public void init() { String prefix = "security.oauth2.client"; boolean defaultSecret = this.credentials.isDefaultSecret(); logger.info(String.format( "Initialized OAuth2 Client\n\n%s.clientId = %s\n%s.secret = %s\n\n", prefix, this.credentials.getClientId(), prefix, defaultSecret ? this.credentials.getClientSecret() : "****")); } } @Configuration @ConditionalOnMissingBean(BaseClientDetails.class) protected static class BaseClientDetailsConfiguration { @Autowired private OAuth2ClientProperties client; @Bean @ConfigurationProperties("security.oauth2.client") public BaseClientDetails oauth2ClientDetails() { BaseClientDetails details = new BaseClientDetails(); if (this.client.getClientId() == null) { this.client.setClientId(UUID.randomUUID().toString()); } details.setClientId(this.client.getClientId()); details.setClientSecret(this.client.getClientSecret()); details.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "password", "client_credentials", "implicit", "refresh_token")); details.setAuthorities( AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); details.setRegisteredRedirectUri(Collections.<String>emptySet()); return details; } } }
當上下文中不存在AuthorizationServerConfigurer接口的實現時,將默認使用OAuth2AuthorizationServerConfiguration實現,默認的服務器受權配置類。
在@EnableAuthorizationServer該註解中有如下註解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer {}
@Configuration @Import(TokenKeyEndpointRegistrar.class) public class AuthorizationServerEndpointsConfiguration { private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer(); @Autowired private ClientDetailsService clientDetailsService; @Autowired private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); @PostConstruct public void init() { for (AuthorizationServerConfigurer configurer : configurers) { try { configurer.configure(endpoints); } catch (Exception e) { throw new IllegalStateException("Cannot configure enpdoints", e); } } endpoints.setClientDetailsService(clientDetailsService); } @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()); return authorizationEndpoint; } @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; } @Bean public CheckTokenEndpoint checkTokenEndpoint() { CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices()); endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter()); endpoint.setExceptionTranslator(exceptionTranslator()); return endpoint; } @Bean public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() { return new WhitelabelApprovalEndpoint(); } @Bean public WhitelabelErrorEndpoint whitelabelErrorEndpoint() { return new WhitelabelErrorEndpoint(); } @Bean public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exception { return getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); } @Bean public ConsumerTokenServices consumerTokenServices() throws Exception { return getEndpointsConfigurer().getConsumerTokenServices(); } /** * This needs to be a <code>@Bean</code> so that it can be * <code>@Transactional</code> (in case the token store supports them). If * you are overriding the token services in an * {@link AuthorizationServerConfigurer} consider making it a * <code>@Bean</code> for the same reason (assuming you need transactions, * e.g. for a JDBC token store). * * @return an AuthorizationServerTokenServices */ @Bean public AuthorizationServerTokenServices defaultAuthorizationServerTokenServices() { return endpoints.getDefaultAuthorizationServerTokenServices(); } public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() { if (!endpoints.isTokenServicesOverride()) { endpoints.tokenServices(defaultAuthorizationServerTokenServices()); } return endpoints; } private Set<HttpMethod> allowedTokenEndpointRequestMethods() { return getEndpointsConfigurer().getAllowedTokenEndpointRequestMethods(); } private OAuth2RequestFactory oauth2RequestFactory() throws Exception { return getEndpointsConfigurer().getOAuth2RequestFactory(); } private UserApprovalHandler userApprovalHandler() throws Exception { return getEndpointsConfigurer().getUserApprovalHandler(); } private OAuth2RequestValidator oauth2RequestValidator() throws Exception { return getEndpointsConfigurer().getOAuth2RequestValidator(); } private AuthorizationCodeServices authorizationCodeServices() throws Exception { return getEndpointsConfigurer().getAuthorizationCodeServices(); } private WebResponseExceptionTranslator exceptionTranslator() { return getEndpointsConfigurer().getExceptionTranslator(); } private TokenGranter tokenGranter() throws Exception { return getEndpointsConfigurer().getTokenGranter(); } private String extractPath(FrameworkEndpointHandlerMapping mapping, String page) { String path = mapping.getPath(page); if (path.contains(":")) { return path; } return "forward:" + path; } @Configuration 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()); } } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; } } }
Register tokenkey endpoint.
@Configuration 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()); } } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; } }
判斷上下文中是否存在JwtAccessTokenConverter的Bean對象,若是存在的話獲取全部該類型的Bean類型的name,若是大於0的話,經過BeanDefinitionBuilder生成TokenKeyEndpoint的BeanDefinitionBuilder。而後經過addConstructorArgReference方法,將該name[0]的對象,做爲構造參數傳遞至TokenKeyEndpoint類,而且在context中註冊該對象。
public class AuthorizationServerEndpointsConfiguration { private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer(); @Autowired private ClientDetailsService clientDetailsService; @Autowired private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); @PostConstruct public void init() { for (AuthorizationServerConfigurer configurer : configurers) { try { configurer.configure(endpoints); } catch (Exception e) { throw new IllegalStateException("Cannot configure enpdoints", e); } } endpoints.setClientDetailsService(clientDetailsService); } .... }
注入全部AuthorizationServerConfigurer的implementation,即繼承AuthorizationServerConfigurerAdapter的類。
configurer.configure(endpoints);
傳遞的對象爲private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
AuthorizationServerEndpointsConfigurer statement is as follows:
/** * Configure the properties and enhanced functionality of the Authorization Server endpoints. */ public final class AuthorizationServerEndpointsConfigurer { ... }
也就是調用如下方法。
/** * Configure the non-security features of the Authorization Server endpoints, like token store, token * customizations, user approvals and grant types. You shouldn't need to do anything by default, unless you need * password grants, in which case you need to provide an {@link AuthenticationManager}. * * @param endpoints the endpoints configurer */ void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;
AuthorizationServerEndpointsConfigurer Core methoh
1.Create default DefaultTokenServices
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; }
2.clientDetailsService
private ClientDetailsService clientDetailsService() { if (clientDetailsService == null) { this.clientDetailsService = new InMemoryClientDetailsService(); } if (this.defaultTokenServices != null) { addUserDetailsService(defaultTokenServices, userDetailsService); } return this.clientDetailsService; }
ClientDetailsService默認是InMemoryClientDetailsService。
3.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; }
受權服務器啓動的時候,會調用AuthorizationServerEndpointsConfigurer.getDefaultAuthorizationServerTokenServices方法。
AuthorizationServerEndpointsConfiguration.defaultAuthorizationServerTokenServices會在啓動時進行初始化爲Bean,初始化默認的defaultAuthorizationServerTokenServices。
AuthorizationServerEndpointsConfigurer.defaultAuthorizationServerTokenServices
/** * This needs to be a <code>@Bean</code> so that it can be * <code>@Transactional</code> (in case the token store supports them). If * you are overriding the token services in an * {@link AuthorizationServerConfigurer} consider making it a * <code>@Bean</code> for the same reason (assuming you need transactions, * e.g. for a JDBC token store). * * @return an AuthorizationServerTokenServices */ @Bean public AuthorizationServerTokenServices defaultAuthorizationServerTokenServices() { return endpoints.getDefaultAuthorizationServerTokenServices(); }
Initialize authorizationEndpoint and TokenEndpint.
@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()); return authorizationEndpoint; } @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; } @Bean public CheckTokenEndpoint checkTokenEndpoint() { CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices()); endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter()); endpoint.setExceptionTranslator(exceptionTranslator()); return endpoint; } @Bean public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() { return new WhitelabelApprovalEndpoint(); } @Bean public WhitelabelErrorEndpoint whitelabelErrorEndpoint() { return new WhitelabelErrorEndpoint(); } @Bean public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exception { return getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); } @Bean public ConsumerTokenServices consumerTokenServices() throws Exception { return getEndpointsConfigurer().getConsumerTokenServices(); } /** * This needs to be a <code>@Bean</code> so that it can be * <code>@Transactional</code> (in case the token store supports them). If * you are overriding the token services in an * {@link AuthorizationServerConfigurer} consider making it a * <code>@Bean</code> for the same reason (assuming you need transactions, * e.g. for a JDBC token store). * * @return an AuthorizationServerTokenServices */ @Bean public AuthorizationServerTokenServices defaultAuthorizationServerTokenServices() { return endpoints.getDefaultAuthorizationServerTokenServices(); } public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() { if (!endpoints.isTokenServicesOverride()) { endpoints.tokenServices(defaultAuthorizationServerTokenServices()); } return endpoints; }
其中comsumerTokenService() and defaultAuthorizationServerTokenServices()都將調用AuthorizationServerEndpointsConfigurer的createDefaultTokenServices方法兩次,前一個方法初始化consumerTokenServices,後一個方法初始化defaultTokenServices。
ConsumerTokenServices and AuthorizationServerTokenServices are interfaces。DefaultTokenServices類同時實現ConsumerTokenServices和AuthorizationServerTokenServices。
AuthorizationServerSecurityConfiguration statement is as follows:
@Configuration @Order(0) @Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class }) public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }
客戶端憑證配置類。初始化客戶端憑證數據,由內存初始化,或者來自jdbc。
@Configuration 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(); } }
Default ClientDetailsService是在ClientDetailsServiceConfiguration類中生成的。由以下方法
@Bean @Lazy @Scope(proxyMode=ScopedProxyMode.INTERFACES) public ClientDetailsService clientDetailsService() throws Exception { return configurer.and().build(); }
受權服務器端點配置類,在@EnableAuthorizationServer中也引入了AuthorizationServerEndpointsConfiguration.class的配置。
UserDetailService
The default userDetailService是在WebSecurityConfigurerAdapter中初始化。
userDetailService是加載用戶特定數據的核心接口,至關於用戶DAO,是該策略。
org.springframework.security.authentication.dao.DaoAuthenticationProvider
* DaoAuthenticationProvider
該接口只有一個只讀方法,用於數據訪問。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
默認實現是UserDetailsServiceDelegator,該類屬於static final class。
聲明於WebSecurityConfigurerAdapter.UserDetailsServiceDelegator。
AuthorizationServerSecurityConfiguration 此類也extends自WebSecurityConfigurerAdapter 。
但通常咱們本身也會聲明一個類來extends此類WebSecurityConfigurerAdapter 。