统一认证中心 Oauth2 高可用坑

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 鉴权服务的高可用保障安全可靠、持续进行服务
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_token是 Oauth2 原生自带的,这里不需要封装。接下来,我们启动服务,在拿到 token 后,通过 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)

我们从上面的日志中,可以发现系统抛出 UnknownHostException 这种异常,无法找到cas-server,但我要说的是:我们这里用到的是Nacos注册中心来实现服务的注册与发现:

image.png

那说明注册的服务可以被发现,接下来我们看支持 LB 的几种服务消费方式: RestTemplate、WebClient、Feign。
我们这里基于 Ribbon,RestTemplate,因为在Oauth2原生中,就是基于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;
    }

大家都知道默认的原生Ribbon,是基于 RestTemplate 的负载均衡,所以这里配置如下:

@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;
}

可以看到,在定义 RestTemplate 的时候,增加了@LoadBalanced注解,但其实在真正调用服务接口的时候,原来host部分是通过手工拼接ip和端口的,直接采用服务名的时候来写请求路径即可。在真正调用的时候,Spring Cloud会将请求拦截下来,然后通过负载均衡器选出节点,并替换服务名为具体的ip和端口,从而实现基于服务名的负载均衡调用。

接下来,我们再看看负载均衡的策略是否有问题,Ribbon默认的负载均衡策略是轮询,内置了多种负载均衡策略,内置的负载均衡的顶级接口为com.netflix.loadbalancer.IRule。具体的策略有:AvailabilityFilteringRule、RoundRobinRule、RetryRule、RandomRule、WeightedResponseTimeRule、BestAvailableRule等。这里直接使用默认的轮询:

@Bean
public IRule ribbonRule(IClientConfig config){
            //return new AvailabilityFilteringRule();
    return new RoundRobinRule();//轮询
    //return new RetryRule();//重试
            //return new RandomRule();//这里配置策略,和配置文件对应
            //return new WeightedResponseTimeRule();//这里配置策略,和配置文件对应
    //return new BestAvailableRule();//选择一个最小的并发请求的server
    //return new MyProbabilityRandomRule();//自定义
}

到这里,目前都还未发现问题,那么既然实现了基于RestTemplate的负载均衡,为什么还是报错呢?

image.png

找了半天,最后发现在Oauth2源码中,注入的是这么个玩意:

image.png

这时候才发现多么的坑,于是乎,一顿猛操作:在资源检验时调用的覆盖其注入:

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

其二,直接set RestTemplate:

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

接下来,我们重启消费端,看看效果,根据之前请求的token,直接访问消费端接口:

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

发现ok了,返回成功200,并且有权限访问该接口:

image.png

总结

有时候自己的代码写的已经很好了,但发现还是无法实现自己想要的:于是乎,可以大胆设想是不是官网源码出了幺蛾子,就像本文一样,如果不一步步检查,怎么也不会发现原来是源码留下如此大的坑,在前面的文章中,其实发现很多源码的不合理之处之后,都在修改,并且生成一套自己的规范返回,这样对于代码本身来说,我们会更加深刻体会、理解。Oauth2源码本身可以只是一个带头的基础功能,后面基于大项目,需要自己对于一些系统的设计进行改造,例如:高可用、高并发鉴权方案、统一认证SSO等等。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
算法 安全 Java
微服务(四)-config配置中心的配置加解密
微服务(四)-config配置中心的配置加解密
|
8月前
|
存储 数据安全/隐私保护 开发者
SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心
OAuth 2.0(开放授权 2.0)是一个开放标准,用于授权第三方应用程序访问用户在资源所有者(用户)的帐户上存储的受保护资源,而无需共享用户凭据。OAuth 2.0 主要用于在互联网上安全地委托授权,广泛应用于身份验证和授权场景。
824 1
|
移动开发 小程序 安全
Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(二)
Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(二)
Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(二)
|
安全 前端开发 小程序
Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(一)
Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(一)
|
安全 Java 微服务
十四.SpringCloud+Security+Oauth2实现微服务授权 - 网关统一鉴权
SpringCloud+Security+Oauth2实现微服务授权 - 网关统一鉴权
|
消息中间件 JavaScript Java
SpringCloud Gateway网关为认证中心和用户微服务构建统一的认证授权入口
SpringCloud Gateway网关为认证中心和用户微服务构建统一的认证授权入口
|
存储 安全 Java
实战!微服务 认证中心 如何扩展授权模式 实现多种方式登录?
实战!微服务 认证中心 如何扩展授权模式 实现多种方式登录?
实战!微服务 认证中心 如何扩展授权模式 实现多种方式登录?
|
存储 JSON 安全
【权限设计系列】「认证授权专题」微服务架构的登陆认证问题
【权限设计系列】「认证授权专题」微服务架构的登陆认证问题
828 0
【权限设计系列】「认证授权专题」微服务架构的登陆认证问题
|
存储 缓存 安全
SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权
OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间、限定范围访问指定资源。   OAuth2中使用token验证用户登录合法性,但token最大的问题是不携带用户信息,资源服务器无法在本地进行验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请求,一是验证token的有效性,二是获取token对应的用户信息。如果有大量的此类请求,无疑处理效率是很低,且认证服务器会变成一个中心节点
1552 57
SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权
|
Java API 微服务
认证鉴权与API权限控制在微服务架构中的设计与实现(一)
作者: [Aoho’s Blog] 引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。
5703 0