Unified certification center Oauth2 high availability pit

Front( Unified certification center Oauth2 certification pit )We use user info URI to realize the authentication information and authorization acquisition judgment of the consumer. Next, we use token info URI to realize authentication and authorization. For specific configuration, see:

cas-server-url: http://cas-server

security:
  path:
    ignores: /,/index,/static/**,/css/**, /image/**, /favicon.ico, /js/**,/plugin/**,/avue.min.js,/img/**,/fonts/**
  oauth2:
    client:
      client-id: rest-service
      client-secret: rest-service-123
      user-authorization-uri: ${cas-server-url}/oauth/authorize
      access-token-uri: ${cas-server-url}/oauth/token
    resource:
      loadBalanced: true
      id: rest-service
      prefer-token-info: true
      token-info-uri: ${cas-server-url}/oauth/check_token
    authorization:
      check-token-access: ${cas-server-url}/oauth/check_token

/ OAuth / check here_ The token is native to Oauth2. No encapsulation is required here. Next, we start the service. After we get the token, we request the consumer through the token:

2021-11-03 16:40:09.057 DEBUG 24652 --- [io2-2001-exec-4] o.s.web.client.RestTemplate              : HTTP POST http://cas-server/oauth/check_token
2021-11-03 16:40:09.060 DEBUG 24652 --- [io2-2001-exec-4] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2021-11-03 16:40:09.062 DEBUG 24652 --- [io2-2001-exec-4] o.s.web.client.RestTemplate              : Writing [{token=[b34841b4-61fa-4dbb-9e2b-76496deb27b4]}] as "application/x-www-form-urlencoded"
2021-11-03 16:40:11.332 ERROR 24652 --- [io2-2001-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://cas-server/oauth/check_token": cas-server; nested exception is java.net.UnknownHostException: cas-server
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:746)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:672)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:581)
    at org.springframework.security.oauth2.provider.token.RemoteTokenServices.postForMap(RemoteTokenServices.java:149)

From the above log, we can find that the system throws UnknownHostException and cannot find the CAS server, but what I want to say is: we use the Nacos registry to register and discover services:

That means registered services can be found. Next, let's look at several service consumption modes supporting LB: RestTemplate, WebClient and Feign.
We are based on Ribbon and RestTemplate here, because in Oauth2 native, we call remote services based on RestTemplate:

private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        if (headers.getContentType() == null) {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        }
        @SuppressWarnings("rawtypes")
        Map map = restTemplate.exchange(path, HttpMethod.POST,
                new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
        @SuppressWarnings("unchecked")
        Map<String, Object> result = map;
        return result;
    }

As we all know, the default native Ribbon is load balancing based on RestTemplate, so the configuration here is as follows:

@LoadBalanced
@Bean
public RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setReadTimeout(env.getProperty("client.http.request.readTimeout", Integer.class, 15000));
        requestFactory.setConnectTimeout(env.getProperty("client.http.request.connectTimeout", Integer.class, 3000));
        RestTemplate rt = new RestTemplate(requestFactory);
        return rt;
}

It can be seen that the @ LoadBalanced annotation is added when defining the RestTemplate, but in fact, when actually calling the service interface, the original host part splices the ip and port manually, and writes the request path directly when using the service name. During the real call, Spring Cloud will intercept the request, select the node through the load balancer, and replace the service name with the specific ip and port, so as to realize the load balancing call based on the service name.

Next, let's see if there is a problem with the load balancing strategy. The default load balancing strategy of Ribbon is polling. A variety of load balancing strategies are built in, and the top-level interface of built-in load balancing is com netflix. loadbalancer. IRule. Specific strategies include: availabilityfiltering rule, roundrobin rule, RetryRule, RandomRule, WeightedResponseTimeRule, BestAvailableRule, etc. The default polling is used directly here:

@Bean
public IRule ribbonRule(IClientConfig config){
            //return new AvailabilityFilteringRule();
    return new RoundRobinRule();//polling 
    //return new RetryRule();// retry 
            //return new RandomRule();// The configuration policy here corresponds to the configuration file
            //return new WeightedResponseTimeRule();// The configuration policy here corresponds to the configuration file
    //return new BestAvailableRule();// Select a server with minimal concurrent requests
    //return new MyProbabilityRandomRule();// custom
}

So far, no problem has been found. Since the RestTemplate based load balancing has been implemented, why still report an error?

After looking for it for a long time, I finally found that in the source code of Oauth2, such a thing was injected:

Only then did we find out how many holes there were, so we did a fierce operation: overwrite the injection called during resource verification:

@Autowired(required = true)
private RemoteTokenServices remoteTokenServices;
    
@Autowired
RestTemplate restTemplate;

Second, set RestTemplate directly:

@Override
    public void configure(ResourceServerSecurityConfigurer resource) throws Exception {
        super.configure(resource);
        
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            // Ignore 400
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400) {
                    super.handleError(response);
                }
            }
        });
        if (Objects.nonNull(remoteTokenServices)) {
            remoteTokenServices.setRestTemplate(restTemplate);
            resource.tokenServices(remoteTokenServices);
        }
        
        resource
        //.tokenStore(tokenStore)
        //.tokenServices(tokenServices)
        .authenticationEntryPoint(customAuthenticationEntryPoint)
        .accessDeniedHandler(customAccessDeniedHandler)
        //.tokenExtractor(new BearerTokenExtractor())
        ;
    }

Next, we restart the consumer to see the effect. According to the previously requested token, we directly access the consumer interface:

2021-11-03 16:57:50.476  INFO 81424 --- [io2-2001-exec-3] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms
2021-11-03 16:57:50.522 DEBUG 81424 --- [io2-2001-exec-3] o.s.web.client.RestTemplate              : HTTP POST http://cas-server/oauth/check_token
2021-11-03 16:57:50.526 DEBUG 81424 --- [io2-2001-exec-3] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2021-11-03 16:57:50.528 DEBUG 81424 --- [io2-2001-exec-3] o.s.web.client.RestTemplate              : Writing [{token=[b34841b4-61fa-4dbb-9e2b-76496deb27b4]}] as "application/x-www-form-urlencoded"
2021-11-03 16:57:50.635 DEBUG 81424 --- [io2-2001-exec-3] o.s.web.client.RestTemplate              : Response 200 OK

If ok is found, success 200 is returned, and you have access to the interface:

summary

Sometimes your code is well written, but you still find that you can't achieve what you want: therefore, you can boldly imagine whether there is a moth in the official website source code. Just like this article, if you don't check it step by step, you won't find such a big hole left by the source code. In the previous article, you actually found many irrationalities in the source code, We are modifying and generating a set of our own standard return, so that we will have a deeper understanding and understanding of the code itself. The Oauth2 source code itself can only be a leading basic function. Based on large projects, you need to transform the design of some systems, such as high availability, high concurrency authentication scheme, unified authentication SSO, etc.

Keywords: Java Load Balance Spring Nacos

Added by Kurt on Wed, 12 Jan 2022 23:21:02 +0200