When we configure OAuth2, we will configure the resource server and authentication server. When our authorization service and authentication service are not in the same service, we can consider using RemoteTokenServices.

If they are in the same service, you don’t need to configure tokenServices, because when ResourceServerConfigurerAdapter is configured, if tokenServices is not configured, a default DefaultTokenServices will be automatically configured. . The two tokenService classes both implement the ResourceServerTokenServices interface.

ResourceServerTokenServices has a total of four implementation classes by default.

ResourceServerTokenServices

Actually, no matter RemoteTokenServices or others, they all implement the ResourceServerTokenServices interface to load access token credentials and retrieve token details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public interface ResourceServerTokenServices {

    /**
     * Load the credentials for the specified access token.
     *
     * @param accessToken The access token value.
     * @return The authentication for the access token.
     * @throws AuthenticationException If the access token is expired
     * @throws InvalidTokenException if the token isn't valid
     */
    OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

    /**
     * Retrieve the full access token details from just the value.
     * 
     * @param accessToken the token value
     * @return the full access token with client id etc.
     */
    OAuth2AccessToken readAccessToken(String accessToken);

}

So, I’ll take a step-by-step approach here, using RemoteTokenServices as an example, to see how the process is done.

1. How to configure RemoteTokenServices?

When you configure the OAuth2 resource server ResourceServerConfigurerAdapter, you can configure tokenService.

1
resources.tokenServices(xxx);

image

1
2
3
4
remoteTokenServices.setClientId(resourceServerProperties.getClientId());
remoteTokenServices.setClientSecret(resourceServerProperties.getClientSecret());
// 设置/oauth/check_token端点
remoteTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());

Configure application.yml :

 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
# oauth2配置
security:
  oauth2:
    client:
      # 客户端ID
      client-id: ${OAUTH2_CLIENT_ID:lzhpo}
      # 客户端秘钥(加密前)
      client-secret: ${OAUTH2_CLIENT_SECRET:lzhpo1024}
      # 授权类型
      grant-type: ${OAUTH2_GRANT_TYPE:authorization_code,password,refresh_token,implicit,client_credentials}
      # 权限范围
      scope: ${OAUTH2_SCOPE:all}
      # 用于密码模式,获取访问令牌的地址(org.springframework.security.oauth2.provider.endpoint.TokenEndpoint)
      access-token-uri: ${OAUTH2_ACCESS_TOKEN_URI:http://localhost:9999/api/auth/oauth/token}
      # 用于授权码模式,获取授权码的地址(org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint)
      user-authorization-uri: ${OAUTH2_USER_AUTHORIZATION_URI:http://localhost:9999/api/auth/oauth/authorize}
    resource:
      # 资源服务器编号
      id: ${spring.application.name}
      # 校验访问令牌是否有效的地址(org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken)
      token-info-uri: ${OAUTH2_TOKEN_INFO_URI:http://localhost:9999/api/auth/oauth/check_token}
      # 获取用户信息
      user-info-uri: ${OAUTH2_USER_INFO_URI:http://localhost:9999/api/auth/oauth/check_user}
      # 默认使用token-info-uri,可以设置为false以使用user-info-uri
      prefer-token-info: true

Then configure on the authentication server (inherits the AuthorizationServerConfigurerAdapter class).

 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
 /**
   * Authorization server security
   *
   * <p>isAuthenticated()、permitAll() <br>
   * If the endpoint /oauth/check_token is Authenticated, header Authorization is required. <br>
   * e.g: {@code Authorization:Basic bHpocG86bHpocG8xMDI0}
   *
   * <pre>
   * Reference:{@link AuthorizationServerProperties}
   *
   * Also can configure in application.properties or application.yml:
   * {@code
   *  security.oauth2.authorization.token-key-access: isAuthenticated()
   *  security.oauth2.authorization.check-token-access: isAuthenticated()
   * }
   * </pre>
   *
   * @param security security
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) {
    security
        // Allow the client to send a form for permission authentication to obtain a token
        .allowFormAuthenticationForClients()
        // Endpoint: /oauth/token_key
        // If you use jwt, the public key that can be obtained is used for token verification
        .tokenKeyAccess("isAuthenticated()")
        // Endpoint: /oauth/check_token
        .checkTokenAccess("isAuthenticated()");
  }

Configuring isAuthenticated() means that authentication is required, set the value of Authorization in the request header to the Base64 value of Basic + Client ID:Client Key.

For example.

1
Authorization:Basic bHpocG86bHpocG8xMDI0

ResourceServerSecurityConfigurer source code (org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer)

ResourceServerSecurityConfigurer

ResourceServerSecurityConfigurer

The key code is here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    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;
    }

2. RemoteTokenServices requests the /oauth/check_token interface

RemoteTokenServices source code (org.springframework.security.oauth2.provider.token.RemoteTokenServices)

RemoteTokenServices

3. OAuth2ClientAuthenticationProcessingFilter Get the result of the request and try to authenticate

Code location: org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter#attemptAuthentication

Key code.

 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
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();
        } catch (OAuth2Exception e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;            
        }
        try {
            // 这里就是前面`RemoteTokenServices`请求`/oauth/check_token`接口,拿到校验结果的
            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
            if (authenticationDetailsSource!=null) {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
                result.setDetails(authenticationDetailsSource.buildDetails(request));
            }
            publish(new AuthenticationSuccessEvent(result));
            return result;
        }
        catch (InvalidTokenException e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;            
        }

    }

4. Load authentication information

It has 3 subclasses, namely org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter , org.springframework.security. ClientCredentialsTokenEndpointFilter , org.springframework.security.oauth2.client.filter. OAuth2ClientAuthenticationProcessingFilter

  • UsernamePasswordAuthenticationFilter : A filter to handle authentication form submissions.
  • ClientCredentialsTokenEndpointFilter : Filter for OAuth2 token endpoints and authentication endpoints.
  • OAuth2ClientAuthenticationProcessingFilter : Gets the OAuth2 access token from the authorization server and loads the authentication object into the SecurityContext.

We are looking at how it is loaded into the SecurityContext, so we focus on the OAuth2ClientAuthenticationProcessingFilter filter.

org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter is the client filter for OAuth2. RemoteServiceto load the identity verification information we got earlier into theSecurityContext`.

It inherits from the org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter abstract class.

attemptAuthentication is an abstract method in the AbstractAuthenticationProcessingFilter abstract class that is given to a subclass to attempt authentication.

AbstractAuthenticationProcessingFilter

Since AbstractAuthenticationProcessingFilter is, after all, a filter that inherits from the GenericFilterBean implementation, take a look at the filter method.

AbstractAuthenticationProcessingFilter

I’ll post the code here to make it easier for me to write comments in the code.

 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
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            // 交给子类实现的尝试进行认证的抽象方法,就上面我说的OAuth2ClientAuthenticationProcessingFilter过滤器的attemptAuthentication
            Authentication authenticationResult = attemptAuthentication(request, response);
            if (authenticationResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                return;
            }
            this.sessionStrategy.onAuthentication(authenticationResult, request, response);
            // Authentication success
            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            // 身份认证成功
            successfulAuthentication(request, response, chain, authenticationResult);
        }
        catch (InternalAuthenticationServiceException failed) {
            this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
            unsuccessfulAuthentication(request, response, failed);
        }
        catch (AuthenticationException ex) {
            // Authentication failed
            unsuccessfulAuthentication(request, response, ex);
        }
    }

successfulAuthentication Source code.

This is the purpose of this article, to load authentication information: SecurityContextHolder.getContext().setAuthentication(authResult);

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authResult) throws IOException, ServletException {
        // 装载身份验证信息
        SecurityContextHolder.getContext().setAuthentication(authResult);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

Reference

  • http://www.lzhpo.com/article/170