General documentation: Article directory Github: github.com/black-ant

A. The preface

In this article, we take a closer look at SpringSecurity and its OAuth2.0 process logic.

2. Easy to use

2.1 the Maven rely on

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
        

<! - request packet -- -- >
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.5. RELEASE</version>
</dependency>
        
Copy the code

2.2 configuration items

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler myAuthenctiationFailureHandler;

    @Autowired
    private AuthorizationServerEndpointsConfiguration endpoints;

    @Autowired
    private UserService userService;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Bean
    public AuthenticationManager authenticationManagerBean(DataSource dataSource) throws Exception {
        OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
        authenticationManager.setTokenServices(new DefaultTokenServices());
        authenticationManager.setClientDetailsService(new JdbcClientDetailsService(dataSource));
        return authenticationManager;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // This method is used for user authentication, where the memory user is added and permissions are specified
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Autowired
    public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
        for(AuthorizationServerConfigurer configurer : configurers) { configurer.configure(clientDetails); }}@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 authorizeEndpointPath = handlerMapping.getServletPath("/oauth/authorize");
        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); }/ / PS: note that the request itself has a WebSecurityConfigurerAdapter, I custom overlay in the options here
        http.authorizeRequests()
                .antMatchers("/test/**").permitAll()
                .antMatchers("/before/**").permitAll()
                .antMatchers("/index").permitAll()
                .antMatchers(authorizeEndpointPath).authenticated()
                .antMatchers(tokenEndpointPath).fullyAuthenticated()
                .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
                .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
                .anyRequest().authenticated()                      // All other requests must be verified before access
                .and()
                .requestMatchers()
// .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
                .and()
                .formLogin()
                .loginPage("/login")                             // Define the login page "/login" to allow access
                .defaultSuccessUrl("/home")  // Default to "list" after successful login
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
                .logout()                                           // The default "/logout" is allowed
                .logoutSuccessUrl("/index")
                .permitAll();
        http.addFilterBefore(new BeforeFilter(), UsernamePasswordAuthenticationFilter.class);
        http.setSharedObject(ClientDetailsService.class, clientDetailsService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Resolve the issue of static resources being intercepted
        web.ignoring().antMatchers("/**/*.js"."/lang/*.json"."/**/*.css"."/**/*.js"."/**/*.map"."/**/*.html"."/**/*.png");
    }

    protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        for(AuthorizationServerConfigurer configurer : configurers) { configurer.configure(oauthServer); }}}Copy the code

Resource Resource configuration

@Configuration
@EnableResourceServer
public class ResServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .tokenStore(tokenStore).resourceId("resourceId");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .requestMatchers()
                .antMatchers("/user"."/res/**")
                .and()
                .authorizeRequests()
                .antMatchers("/user"."/res/**") .authenticated(); }}Copy the code

OAuthConfig exclusive property

@Configuration
@EnableAuthorizationServer
@Order(2)
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private DataSource dataSource;

    @Autowired
    @Lazy
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore(a) {
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService(a) {
        return new JdbcClientDetailsService(dataSource);
    }

    @Bean
    public ApprovalStore approvalStore(a) {
        return new JdbcApprovalStore(dataSource);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(a) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    // Check the token policy
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.allowFormAuthenticationForClients();
        security.tokenKeyAccess("isAuthenticated()");
        security.checkTokenAccess("permitAll()");
    }

    // Master configuration information of OAuth2
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
// .approvalStore(approvalStore()).authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices()) .tokenStore(tokenStore()); }}Copy the code

2.3 database

See the project

2.4 Usage Mode

Request way

http://localhost:8080/security/oauth/authorize? response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=app

https://www.baidu.com/?code=jYgDO3

Copy the code

AccessToken

var settings = {
  "url": "http://localhost:8080/security/oauth/token"."method": "POST"."timeout": 0."headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  "data": {
    "grant_type": "authorization_code"."client_id": "client"."client_secret": "secret"."code": "CFUFok"."redirect_uri": "http://www.baidu.com"}}; $.ajax(settings).done(function (response) {
  console.log(response);
});

/ / fail
{
    "error": "invalid_grant"."error_description": "Invalid authorization code: CFUFok"
}

/ / success
{
    "access_token": "c0955d7f-23fb-4ca3-8a52-c715867cbef2"."token_type": "bearer"."refresh_token": "55f53af0-1133-46dc-a32d-fbb9968e5938"."expires_in": 7199."scope": "app"
}

Copy the code

check Token

var settings = {
  "url": "http://localhost:8080/security/oauth/check_token? token=c0955d7f-23fb-4ca3-8a52-c715867cbef2"."method": "GET"."timeout": 0}; $.ajax(settings).done(function (response) { console.log(response); });/ / return
{
    "aud": [
        "resourceId"]."exp": 1618241690."user_name": "gang"."client_id": "client"."scope": [
        "app"]}Copy the code

3. Source parsing

3.1 the base class

TokenStore

TokenStore is an interface, and being an interface means that it can be completely customized in use

public interface TokenStore {

	// Obtain an OAuth2Authentication from the OAuth2AccessToken object
	OAuth2Authentication readAuthentication(OAuth2AccessToken token);
	OAuth2Authentication readAuthentication(String token);

	// Persistently associate OAuth2AccessToken and OAuth2Authentication
	void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);

	// Fetch and remove OAuth2AccessToken
	OAuth2AccessToken readAccessToken(String tokenValue);
	void removeAccessToken(OAuth2AccessToken token);
	OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

	// OAuth2RefreshToken direct operation
	void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);
	OAuth2RefreshToken readRefreshToken(String tokenValue);
	OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);
	void removeRefreshToken(OAuth2RefreshToken token);

	// Remove the access token with the refresh token. This method is used to control the number of tokens
	void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken);

        // Client ID Query token
	Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);
	Collection<OAuth2AccessToken> findTokensByClientId(String clientId);
}

Copy the code

Let’s take a look at the main TokenStore implementation classes, which provide the following implementations by default:

InMemoryTokenStore

  • As you can see from the name, this class puts the Token in memory,
  • If you click on it, you can see that there’s a lot of ConcurrentHashMap in the class, to make it safe for multithreaded access
// The only special thing is that it is not a set, but a set for each business. This is actually equivalent to the processing idea of dividing the database into tables
C- InMemoryTokenStore
    - private final ConcurrentHashMap<String, OAuth2AccessToken> accessTokenStore
    - private final ConcurrentHashMap<String, OAuth2AccessToken> authenticationToAccessTokenStore 
    - private final ConcurrentHashMap<String, Collection<OAuth2AccessToken>> userNameToAccessTokenStore 
    
// Internal category TokenExpiryPSC- TokenExpiry implements Delayed ? - Delayed is the interface for delaying processing, which is used to determine whether the Token has expired -private final long expiry;
    - private final String value;

Copy the code

JdbcTokenStore

JdbcTokenStore is a viable solution, but it is not the optimal solution. Database processing poses challenges for high concurrency and high performance

// Select * from 'Set' where 'Set' = 'Set'
private static final String DEFAULT_ACCESS_TOKEN_INSERT_STATEMENT =  "insert into oauth_access_token (toke....."
private String insertAccessTokenSql = DEFAULT_ACCESS_TOKEN_INSERT_STATEMENT;

// Key point 2: Use JDBCTemplate, meaning regular Spring configuration will do
private final JdbcTemplate jdbcTemplate;  

Copy the code

RedisTokenStore

Redis storage Token is a common storage method. Generally, it is the preferred solution. JDBC is used only when the environment cannot be used

  • Colons distinguish folders
  • RedisConnectionFactory requires the Redis package
  • JdkSerializationStrategy Serialization strategy
    • Serialization is the most important area of concern, and some monitoring frameworks may conflict with serialization

There are two other special implementation classes that are not a way to persist

JwkTokenStore

  • Support is provided for JSON Web signature (JWS) validation of JSON Web token (JWT) using JSON Web Key (JWK)
  • The token library implementation is dedicated to the resource server with the sole responsibility of decoding the JWT and verifying its signature (JWS) with the corresponding JWK
  • As you can see from this introduction, it is used for the resource server and its main purpose is transformation, so it is not difficult to click on it and see that there is an object inside for the underlying call
private finalTokenStore delegate: Use this object to handle the underlying way// The common constructor
public JwkTokenStore(String jwkSetUrl) 
public JwkTokenStore(List<String> jwkSetUrls)
public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter)
public JwkTokenStore(String jwkSetUrl, JwtClaimsSetVerifier jwtClaimsSetVerifier)
public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter,JwtClaimsSetVerifier jwtClaimsSetVerifier)
public JwkTokenStore(List<String> jwkSetUrls, AccessTokenConverter accessTokenConverter,JwtClaimsSetVerifier jwtClaimsSetVerifier)


Copy the code

Extended information:

  • JWK RFC: tools.ietf.org/html/rfc751…
  • JWT RFC: tools.ietf.org/html/rfc751…
  • JWS RFC: tools.ietf.org/html/rfc751…

JwtTokenStore

This object is actually a new system, a JWT implementation of tokens, not just a way to store them

- private JwtAccessTokenConverter jwtTokenEnhancer;
- private ApprovalStore approvalStore;
    - JdbcApprovalStore
    - TokenApprovalStore
    - InMemoryApprovalStore  
Copy the code
  • You can see that a member variable for transformation and a store object are provided
  • The important thing to note here is that he only reads from the token itself. It’s not really storage, it never persists anything.
    • Because, JWT itself stores the data

3.2 Event Type and Processing

Events are used to push, the main use of DefaultAuthenticationEventPublisher, let’s take a look at him

DefaultAuthenticationEventPublisher

The constructor gives you a rough idea of the event type

public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {

    this.applicationEventPublisher = applicationEventPublisher;
    addMapping(BadCredentialsException.class.getName(),AuthenticationFailureBadCredentialsEvent.class);
    addMapping(UsernameNotFoundException.class.getName(),AuthenticationFailureBadCredentialsEvent.class);
    addMapping(AccountExpiredException.class.getName(),AuthenticationFailureExpiredEvent.class);
    addMapping(ProviderNotFoundException.class.getName(),AuthenticationFailureProviderNotFoundEvent.class);
    addMapping(DisabledException.class.getName(),AuthenticationFailureDisabledEvent.class);
    addMapping(LockedException.class.getName(),AuthenticationFailureLockedEvent.class);
    addMapping(AuthenticationServiceException.class.getName(),AuthenticationFailureServiceExceptionEvent.class);
    addMapping(CredentialsExpiredException.class.getName(),AuthenticationFailureCredentialsExpiredEvent.class);
    addMapping(	"org.springframework.security.authentication.cas.ProxyUntrustedException", AuthenticationFailureProxyUntrustedEvent.class); } M- publishAuthenticationSuccess ? M - release certification success events - publishAuthenticationFailure - AbstractAuthenticationEvent event = constructor. NewInstance (authentication,  exception); ? - build a AbstractAuthenticationEvent - applicationEventPublisher. PublishEvent (event)? - M - setAdditionalExceptionMappings release event? - Set additional exceptions to event mappings. They are automatically merged with the default exception of the event mapping defined by the ProviderManagerCopy the code

3.3 Service processing class

ResourceServerTokenServices interface

public interface ResourceServerTokenServices {
	OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
	OAuth2AccessToken readAccessToken(String accessToken);
}
Copy the code

DefaultTokenServices

Default Token handling class, see nothing special, mainly calls to TokenStore

RemoteTokenServices

  • Query the /check_token endpoint to get the contents of the access token. If the endpoint returns a 400 response, this indicates that the token is invalid.
C- RemoteTokenServices
    F- privateRestOperations restTemplate; - Simply call the check_token interface from this object to query the token information - note that, unlike the local class, this method should be intended for the current OAuth service as an SPCopy the code

3.4 Token Management system

TokenGranter is an interface that has a bunch of implementation classes,

One of the most common should be AuthorizationCodeTokenGranter and ImplicitTokenGranter, RefreshTokenGranter

C - AuthorizationCodeTokenGranter M - getOAuth2Authentication - Paramters in Code, And sentenced to empty - > InvalidRequestException - authorizationCodeServices. ConsumeAuthorizationCode (authorizationCode) : Through the Code to obtain OAuth2Authentication - whether redirectUri and clientId exist - > RedirectMismatchException/InvalidClientExceptionCopy the code

3.5 Token Conversion system

C- DefaultAccessTokenConverter ? - Default Token processing system, let's take a look at the main M- convertAccessToken? - As you can see, there are different switches throughout the conversion logic to determine which ones to displayCopy the code

3.6 Configuration Details

OAuth provides automatic configuration of the OAuth protocol. OAuth provides automatic configuration of the OAuth protocol. OAuth contains two classes:

  • Implement User authentication
    • AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter
  • Implement Resource authentication
    • ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered


// ResourceServerConfiguration
    F- private TokenStore tokenStore; // Token management implementation
    F- private AuthenticationEventPublisher eventPublisher; // Event publishing
    F- private Map<String, ResourceServerTokenServices> tokenServices; // Token Service collection
    F- private ApplicationContext context; 
    F- privateList<ResourceServerConfigurer> configurers = Collections.emptyList(); ? - The collection here can be used to customize the ResourceServerConfigurer class F-privateAuthorizationServerEndpointsConfiguration endpoints; ? Psc-notoauthrequestmatcher M- configure(HttpSecurity HTTP)? - the core configuration method, mainly produces a ResourceServerSecurityConfigurer in HttpSecurity? - This is actually a clone of the current object to HttpSecurity, not a reference - the previous steps are: Configure tokenServices + tokenStore + eventPublisher - and then find an interesting place: Structurally, this should be an application of decoratorsfor(ResourceServerConfigurer configurer : configurers) { configurer.configure(resources); } - The next steps to start configuring HttpSecurity itself are -authenticationProvider: AnonymousAuthenticationProvider - exceptionHandling - accessDeniedHandler - sessionManagement : Session management - sessionCreationPolicy - handling CSRF across domains - adding requestMatcher - and then finding another interesting place where both parties hold objects to each otherfor (ResourceServerConfigurer configurer : configurers) {
			configurer.configure(http);
		}

// AuthorizationServerSecurityConfiguration 
protected void configure(HttpSecurity http) throws Exception {
    
    / / same as above, but to build a new AuthorizationServerSecurityConfigurer in HttpSecurity
    AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
    
    // 
    FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
    http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
    
    // Decorators hold objects for more extended functionality
    configure(configurer);
    http.apply(configurer);
    
    // Get the OAuth related interface and configure the corresponding permission requirements below
    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); }/ / a little... Nothing critical. It's all generic, right
    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);
		http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
Copy the code

4. Running logic

Having looked at the configuration logic, let’s look at the main operation logic:

4.1 Request Entry

Let’s start with the entry authorize of the request and see what path the request took to get here

Process started

Skipping a series of Invoke and MVC processes to find the FilterChainProxy class, you can probably see that the OAuth protocol filters requests within the Filter overall process

Let’s find out which filter class is blocking requests to the login page:

Step 1 : SecurityContextPersistenceFilter

This is mainly done in finally after the Filter chain is executed

Step 2 : BasicAuthenticationFilter

As you can see, the code does these things

C01- BasicAuthenticationFilter
    String header = request.getHeader("Authorization"); ? - Basic Z2FuZzoxMjM0NTY=// If the header is empty, the Filter is continued
    if (header == null| |! header.toLowerCase().startsWith("basic ")) {
        chain.doFilter(request, response);
        return;
    }
    
    // If the authentication information exists
    - String[] tokens = extractAndDecodeHeader(header, request);
    - String username = tokens[0];
    - new UsernamePasswordAuthenticationToken(username, tokens[1]); ? - build a UsernamePasswordAuthenticationToken -... (PS: the logic Filter detailed said, don't repeatedly said) - SecurityContextHolder. GetContext () setAuthentication (authResult);Copy the code

The logic of Filter has already been explained before, so I won’t go into too much detail here

Other extended Filter

TokenEndpointAuthenticationFilter

Optional authentication filter for TokenEndpoint. It is located in the client’s another filter downstream (usually BasicAuthenticationFilter), if the request is also contains the user credentials, it would be for Spring SecurityContext create a OAuth2Authentication.

If you use this filter, the Spring security context will contain an OAuth2Authentication encapsulation (as an authorization request), the form parameters that enter the filter, and the client ID that authenticates from the already authenticated client. And the authenticated user token extracted from the request and authenticated using the authentication manager.

OAuth2AuthenticationProcessingFilter

Pre-authentication filters for OAuth2 protected resources.

Extracted from the incoming request a OAuth2 token, and use it with OAuth2Authentication (if used) with OAuth2AuthenticationManager filled with Spring security context.

4.2 Interface Details

Note that by the time of this interface, authentication has actually been completed. See Filter above for the interception process. This part only analyzes the internal process

Interface 1: Authorize

http://localhost:8080/security/oauth/authorizeC06- AuthorizationEndpoint M601- authorize P- Map<String, Object> model P- Map<String, String> parameters : SessionStatus P- Principal Principal: Because, in fact, already finished certification, so can get Principal - getOAuth2RequestFactory () createAuthorizationRequest (parameters); ? - through the parameters generated a AuthorizationRequest, the flow in the process of the object for authentication object - AuthorizationRequest. GetResponseTypes () : Get Set<String> of tResponseTypes? - if the collection type correctly - > UnsupportedResponseTypeException? - TODO: Why set? - authorizationRequest. GetClientId () : check ClientId exists - > InvalidClientException - principal. IsAuthenticated () : Check whether the certification - > InsufficientAuthenticationException - authorizationRequest. SetRedirectUri (resolvedRedirect) : to generate and set up the redirection address? - note that this address at this time also without a Code - oauth2RequestValidator. ValidateScope (authorizationRequest, client)? - check whether the scope of the current client contains the current request - userApprovalHandler. CheckForPreApproval (authorizationRequest, (Authentication) principal)? - TODO - userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal) ? - whether the request has been approved by the end user (or other processes) - getAuthorizationCodeResponse (authorizationRequest, (Authentication) principal)? M602? ResponseType = code - ResponseType = token when the final processing logic: M605 M602 getAuthorizationCodeResponse - getSuccessfulRedirect(authorizationRequest,generateCode(authorizationRequest, authUser)):M603 M603- getSuccessfulRedirect - Map<String, String> query =new LinkedHashMap<String, String>();
        - query.put("code", authorizationCode); ? - insert the Code - String state = authorizationRequest. GetState (); -if(state ! =null) query.put("state", state); ? - Insert State M604 -Generatecode -oAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest); - OAuth2Authentication combinedAuth =newOAuth2Authentication(storedOAuth2Request, authentication); - String code = authorizationCodeServices.createAuthorizationCode(combinedAuth); ? - core method, pay attention to the AuthorizationCodeServices here is an interface, means that the object can be custom implementation? - the generated classes here is RandomValueStringGenerator// Add the Token modeWhat happens here when it comes to using Implicit mode for authentication? M605- getImplicitGrantResponse(authorizationRequest) - TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest,"implicit"); - OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest); - OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request); ? - This method generates the last Token, and raises an exception if it is null - getTokenGranter().grant()"implicit".new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));

Copy the code

Interface 2: AccessToken Interface/oAuth /token

C07- TokenEndpoint ? - The Token processing is mainly concentrated in the class, which provides the POST and GET request capabilities2M701- postAccessToken(Principal Principal,Map<String, String> parameters) - Determine whether authentication has been performed - getClientDetailsService().loadClientByClientid (clientId)? - Get clientId first, then get a ClientDetails - getOAuth2RequestFactory().createToKenRequest (Parameters, authenticatedClient)? - Build a TokenRequest - check the Client ID, and then check the ClientDetails Scope - check whether the GrantType is reasonable, can not be empty, Cannot be implicit - tokenRequest. SetScope (OAuth2Utils parseParameterList (parameters) get (OAuth2Utils. SCOPE))); ? - process - getTokenGranter RefreshToken type () grant (tokenRequest. GetGrantType (), tokenRequest)? CodeToken generation logic - getResponse(token) : Generates a Response objectCopy the code

Interface 3: CheckTokenEndpoint – / oAuth /check_token

C08- CheckTokenEndpoint M801- checkToken - OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);  ? - Obtain the OAuth2AccessToken object using the Token. - OAuth2Authentication Authentication = resourceServerTokenServices.loadAuthentication(token.getValue()); ? - if OAuth2AccessToken exist and not expired, obtain OAuth2Authentication - accessTokenConverter. ConvertAccessToken (token, authentication) ? - Returns user informationCopy the code

Client Core processing

The Client is also a very core concept in OAuth, and not surprisingly, Client validation is still handled through filters

C- ClientCredentialsTokenEndpointFilter
    M- attemptAuthentication
        - String clientId = request.getParameter("client_id");
        - String clientSecret = request.getParameter("client_secret"); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); ? - if the certification, it is returned directly (PS: this is the Client authentication) - UsernamePasswordAuthenticationToken authRequest =newUsernamePasswordAuthenticationToken(clientId,clientSecret); ? - build a UsernamePasswordAuthenticationToken for authentication Client - ihs. GetAuthenticationManager () authenticate (authRequest)// ProviderManager is then called for authentication processingC-providermanager m-authenticate for-getproviders () -result = provider.authenticate(authentication) : Completes authentication// DaoAuthenticationProvider
Copy the code

As you can see, the Client is completely authenticated as a user, and the whole system is universal

Service processing: OAuth2ErrorHandler Exception handling

TODO

AuthenticationProvider architecture

The overall architecture of the AuthenticationProvider, which is the main body of authentication, is extremely large

TODO

4.3 Other important tool classes

DefaultStateKeyGenerator

The default state build tool

private RandomValueStringGenerator generator = newRandomValueStringGenerator(); ? - Use random numbersCopy the code

4.4 exception class

  • OAuth2AccessDeniedException
    • We usually want a 403 when access is denied, but we want the same treatment as all other OAuth2Exception types
  • UserApprovalRequiredException
    • License exception
  • UserRedirectRequiredException
    • This exception is thrown, allowing the token to be redirected
  • AccessTokenRequiredException
  • JwkException

4.5 supplementary class

OAuth2RestTemplate

OAuth2’s custom RestTemplate makes OAuth2 authenticated Rest requests using the credentials of the provided resources

// This includes some customizations related to OAuthAppendQueryParameter: builds the parameter of the token request. - acquireAccessToken: builds an OAuth2AccessToken?? - getAccessToken: Access token to get or update the current context if necessary Create a request, can call DefaultOAuth2RequestAuthenticator to generate a Token C - DefaultOAuth2RequestAuthenticator? - Generate an OAuth2Request-authorization Bearer via AccessToken....Copy the code

ProviderDiscoveryClient

OAuth2 should also support OIDC. This class is used to discover clients of providers configured according to the OIDC specification

public ProviderConfiguration discover(a) {

    // Initiate a request
    Map responseAttributes = this.restTemplate.getForObject(this.providerLocation, Map.class);
    ProviderConfiguration.Builder builder = new ProviderConfiguration.Builder();
    
    // Obtain OIDC information
    builder.issuer((String)responseAttributes.get(ISSUER_ATTR_NAME));
    builder.authorizationEndpoint((String)responseAttributes.get(AUTHORIZATION_ENDPOINT_ATTR_NAME));    
    if (responseAttributes.containsKey(TOKEN_ENDPOINT_ATTR_NAME)) {
        builder.tokenEndpoint((String)responseAttributes.get(TOKEN_ENDPOINT_ATTR_NAME));
    }
    if (responseAttributes.containsKey(USERINFO_ENDPOINT_ATTR_NAME)) {
        builder.userInfoEndpoint((String)responseAttributes.get(USERINFO_ENDPOINT_ATTR_NAME));
    }
    if (responseAttributes.containsKey(JWK_SET_URI_ATTR_NAME)) {
        builder.jwkSetUri((String)responseAttributes.get(JWK_SET_URI_ATTR_NAME));
    }

    return builder.build();
}

Copy the code

// You can basically see these OIDC attributes
private static final String PROVIDER_END_PATH = "/.well-known/openid-configuration";
private static final String ISSUER_ATTR_NAME = "issuer";
private static final String AUTHORIZATION_ENDPOINT_ATTR_NAME = "authorization_endpoint";
private static final String TOKEN_ENDPOINT_ATTR_NAME = "token_endpoint";
private static final String USERINFO_ENDPOINT_ATTR_NAME = "userinfo_endpoint";
private static final String JWK_SET_URI_ATTR_NAME = "jwks_uri";

Copy the code

4.6 Extended Service Customization

Our ultimate goal is to know which nodes can be extended:

Security can be extended in the following ways:

  • Where the interface is used
    • TokenStore
    • ResourceServerTokenServices
    • ClientDetailsService
    • AuthorizationServerConfigurer
  • Where the switch is used
    • Extend Filter to listen on AccessToken
    • OAuth2AuthenticationFailureEvent
    • OAuth2ClientAuthenticationProcessingFilter
  • Where the Set method is provided

TODO: it’s useless to just talk about it, I’ll try TODO a Demo later

conclusion

On the whole, I went a general way, but there are still some scattered points that I don’t want to go, and my energy is limited. If it is involved, I will perfect it

Reference and thanks

Blog.csdn.net/weixin_4184…