十三.SpringCloud+Security+Oauth2实现微服务授权 - 服务之间授权

简介: SpringCloud+Security+Oauth2实现微服务授权 - 服务之间授权

1.服务之间授权方案

原理其实很简单,我们的调用关系是客户端(浏览器)调用资源服务器A通过请求头传递Token,资源服务器A通过Feign调用资源服务器B请求是没有Token的,我们只需要编写一个Feign的拦截器,将客户端请求A的请求头中的Token设置到资源服务器A调用资源服务器B的Feign的请求头中即可
image.png

2.搭建第二个资源服务器

这里需要搭建第二个资源服务器来演示服务器之间的授权,只需要把security-resource-server 复制一份命名为 security-resource2-server 即可,当然security-resource-server 需要集成Feign去调用security-resource2-server ,这里就省略了,没有思路??您可以参照 《负载均衡Feign

这里还要注意一个问题,就是用户如果要能访问到资源服务器A和资源服务器B,那么它应该同时拥有资源服务器A和资源服务器B的访问权限(permisson),所以不要忘记给用户把需要的权限都配置上

3.Feign的拦截器

为了通用,我们可以搭建一个公共的模块编写Feign的拦截器,谁需要转发Token就只需要依赖它即可 , 搭建模块 “security-resource-common” ,

3.1.导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

3.2.定义Feign的拦截器

RequestInterceptor 是Feign的拦截器接口,提供了apply方法让我们可以通过RequestTemplate 对请求进行自定义,注意:该拦截器类需要给Spirng扫描到

@Component
@Slf4j
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
   
   

    //请求头中的token
    private final String AUTHORIZATION_HEADER = "Authorization";

    @Override
    public void apply(RequestTemplate requestTemplate) {
   
   
        //requestTemplate:feign底层用来发封装请求的对象

        //1.获取请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        //2.获取请求头中的Token
        String token = requestAttributes.getRequest().getHeader(AUTHORIZATION_HEADER);
        log.info("Feign拦截器添加请求头:{}",token);

        //3.添加到Feign的请求头
        requestTemplate.header(AUTHORIZATION_HEADER,token);
    }
}
代码挺简单的,通过RequestContextHolder得到请求对象,获取请求头中的Token设置到RequestTemplate 的header中即可

4.修改Hystrix并发策略

4.1.问题描述

理论上来说做了如上配置即可完成Token的转发了,但如果我们集成了Hystrix那么在Feign的拦截器中是没办法获取到请求对象的,这是因为Hystrix默认的隔离策略是线程池,每个请都会被分配到一个新的线程执行,导致请对象无法获取,不知道如何降级?见《Feign开启Hystrix

4.2.解决方案

解决方案有两种,一是使用信号量隔离,在配置文件中加入如下配置:“hystrix.command.default.execution.isolation.strategy=SEMAPHORE”,解决方案二是修改Hystrix的隔离策略,我们这里使用第二种方式,因为使用信号量隔离会让请求变成单线程执行,官方也不推荐。
image.png

如何修改Hystrix的隔离策略呢?原理就是把调用线程A中的请求通过RequestContextHolder获取到,放到新的线程B中的RequestContextHolder中即可 。

4.3.定义Hystrix并发策略配置

@Configuration
public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
   
   

    private HystrixConcurrencyStrategy hystrixConcurrencyStrategy;

    public FeignHystrixConcurrencyStrategy() {
   
   
        try {
   
   
            this.hystrixConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.hystrixConcurrencyStrategy instanceof FeignHystrixConcurrencyStrategy) {
   
   
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook =
                    HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy =
                    HystrixPlugins.getInstance().getPropertiesStrategy();

            HystrixPlugins.reset();
            HystrixPlugins instance = HystrixPlugins.getInstance();
            instance.registerConcurrencyStrategy(this);
            instance.registerCommandExecutionHook(commandExecutionHook);
            instance.registerEventNotifier(eventNotifier);
            instance.registerMetricsPublisher(metricsPublisher);
            instance.registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
   
   
            System.out.println("策略注册失败");
        }
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
   
   
        //线程A获取请求对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //把请求对象放到新的线程中
        return new WrappedCallable<>(callable, requestAttributes);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime,
                                            TimeUnit unit, BlockingQueue<Runnable> workQueue) {
   
   
        return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixThreadPoolProperties threadPoolProperties) {
   
   
        return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
   
   
        return this.hystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
   
   
        return this.hystrixConcurrencyStrategy.getRequestVariable(rv);
    }

    static class WrappedCallable<T> implements Callable<T> {
   
   
        private final Callable<T> target;
        private final RequestAttributes requestAttributes;

        public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
   
   
            this.target = target;
            this.requestAttributes = requestAttributes;
        }

        @Override
        public T call() throws Exception {
   
   
            try {
   
   
                //把A线程传入过来的请求对象,设置到B线程的RequestContextHolder
                RequestContextHolder.setRequestAttributes(requestAttributes);
                return target.call();
            } finally {
   
   
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
}

到这里拦截器模块就搭建结束了,你还需要把这个模块依赖到资源服务器中,即给“security-resource-server” 导入依赖 “security-resource-common”,然后启动服务测试。

5.服务之间授权-客户端模式

5.1.问题描述

上面服务之间授权的场景是用户发起的请求需求多个服务完成,然后有了服务之间的授权,有一种场景就是服务之间的调用可能和用户上下文无关。

比如有这样一个场景,由于项目设计的问题,在做微服务重构时导致认证中心AuthServer在做认证时需要调用另外一个微服务SystemServer来加载用来的权限列表,即:用户认证表和用户权限表不在同一个数据库中,当然解决方案也有很多,比如强行把权限和认证表放在一个库,这个不是今天讨论的重点。

现在的问题是SystemServer是做了权限控制的,也就是说客户端向AuthServer提交认证请求获取Token,而AuthServer需要调用SystemServer加载用户的权限才能生成Token,而加载权限又需要先得有一个Token才行,这不就矛盾了么?

这里我要说的解决方案就是给AuthServer添加一个Feign的拦截器,在拦截器中采用客户端模式生成一个临时的Token向SystemServer发起请求加载权限列表然后再为客户端颁发正式的Token

为什么采用客户端模式?因为服务之间是绝对信任的,而且只是需要颁发一个临时的Token而已,和用户上下文无关,这个Token并不是为用户生成的Token,只是服务之间调用需要的临时Token

5.2.Feign的拦截器

这次的拦截器和上一篇文章的拦截器的定义方式一样,只不过上一次是转发用户的Token,而这一次是自己生成一个临时的Token,代码如下:

@Component
@Slf4j
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
   
   

    private static String TEMPTOKENURL = "http://localhost:3000/oauth/token?client_id=%s&client_secret=%s&grant_type=client_credentials";

    //请求头中的token
    private final String AUTHORIZATION_HEADER = "Authorization";

    @Override
    public void apply(RequestTemplate requestTemplate) {
   
   
        //requestTemplate:feign底层用来发请求的http客户端
        //1.使用客户端模式生成一个临时的Token
        Map<String, String> tokenMap = HttpUtil.sendPost(String.format(TEMPTOKENURL, "temp", "123"));
        ValidUtils.assertNotNull(tokenMap,"服务调用失败[临时Token获取失败]");

        log.info("Feign拦截器添加临时Token到请求头:{}",tokenMap);

        //2.添加到Feign的请求头
        requestTemplate.header(AUTHORIZATION_HEADER,"Bearer "+tokenMap.get("access_token"));
    }
}

5.4.Http工具类

http工具用到了httpclient包

 <dependencies>
     <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpclient</artifactId>
         <version>4.5.5</version>
     </dependency>
   <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.59</version>
     </dependency>
 </dependencies>

工具类

public class HttpUtil {
   
   

    //发送Post请求,注意:参数使用?方式带着URL后面
    public static Map<String,String> sendPost(String url) {
   
   
        // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        // 创建Post请求
        HttpPost httpPost = new HttpPost(url);
        // 响应模型
        CloseableHttpResponse response = null;
        try {
   
   
            // 由客户端执行(发送)Post请求
            response = httpClient.execute(httpPost);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
   
   
                return JSON.parseObject(EntityUtils.toString(responseEntity),Map.class);
            }
        } catch (Exception e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            try {
   
   
                // 释放资源
                if (httpClient != null) {
   
   
                    httpClient.close();
                }
                if (response != null) {
   
   
                    response.close();
                }
            } catch (IOException e) {
   
   
                e.printStackTrace();
            }
        }
        return null;
    }
}

总结

到这里就结束了,文章介绍了两种微服务之间的授权方式,一种是和用户上下文有关的请求,通过Feign的拦截器转发用户请求中的Token的给下游微服务,二种是微服务之间调用可能和当前用户上下文无关,我们采用在拦截器中采用客户端模式生成一个Token转发给下游服务的方式,希望对你有所帮助

相关文章
|
21天前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
2月前
|
Cloud Native Java API
聊聊从单体到微服务架构服务演化过程
本文介绍了从单体应用到微服务再到云原生架构的演进过程。单体应用虽易于搭建和部署,但难以局部更新;面向服务架构(SOA)通过模块化和服务总线提升了组件复用性和分布式部署能力;微服务则进一步实现了服务的独立开发与部署,提高了灵活性;云原生架构则利用容器化、微服务和自动化工具,实现了应用在动态环境中的弹性扩展与高效管理。这一演进体现了软件架构向着更灵活、更高效的方向发展。
|
2月前
|
存储 数据可视化 Java
基于MicrometerTracing门面和Zipkin实现集成springcloud2023的服务追踪
Sleuth将会停止维护,Sleuth最新版本也只支持springboot2。作为替代可以使用MicrometerTracing在微服务中作为服务追踪的工具。
157 1
|
7天前
|
NoSQL 前端开发 测试技术
👀探秘微服务:从零开启网关 SSO 服务搭建之旅
单点登录(Single Sign-On,简称SSO)是一种认证机制,它允许用户只需一次登录就可以访问多个应用程序或系统。本文结合网关和SaToken快速搭建可用的Session管理服务。
47 8
|
26天前
|
弹性计算 持续交付 API
构建高效后端服务:微服务架构的深度解析与实践
在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
|
23天前
|
弹性计算 Kubernetes API
构建高效后端服务:微服务架构的深度剖析与实践####
本文深入探讨了微服务架构的核心理念、设计原则及实现策略,旨在为开发者提供一套系统化的方法论,助力其构建灵活、可扩展且易于维护的后端服务体系。通过案例分析与实战经验分享,揭示了微服务在提升开发效率、优化资源利用及增强系统稳定性方面的关键作用。文章首先概述了微服务架构的基本概念,随后详细阐述了其在后端开发中的应用优势与面临的挑战,最后结合具体实例,展示了如何从零开始规划并实施一个基于微服务的后端项目。 ####
|
1月前
|
监控 持续交付 数据库
构建高效的后端服务:微服务架构的深度解析
在现代软件开发中,微服务架构已成为提升系统可扩展性、灵活性和维护性的关键。本文深入探讨了微服务架构的核心概念、设计原则和最佳实践,通过案例分析展示了如何在实际项目中有效地实施微服务策略,以及面临的挑战和解决方案。文章旨在为开发者提供一套完整的指导框架,帮助他们构建出更加高效、稳定的后端服务。
|
2月前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
1月前
|
Kubernetes API Docker
构建高效后端服务:微服务架构的深度实践与优化####
本文深入探讨了微服务架构在现代后端开发中的应用,通过剖析其核心概念、设计原则及实施策略,结合具体案例分析,展示了如何有效提升系统的可扩展性、可靠性和维护性。文章还详细阐述了微服务拆分的方法论、服务间通信的最佳实践、以及容器化与编排工具(如Docker和Kubernetes)的应用技巧,为读者提供了一份全面的微服务架构落地指南。 ####
|
1月前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
65 3

热门文章

最新文章