服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 教你Spring Cloud保证各个微服务之间调用安全性

教你Spring Cloud保证各个微服务之间调用安全性

2021-11-26 11:45码农架构 Java教程

在微服务的架构下,系统会根据业务拆分为多个服务,各自负责单一的职责,在这样的架构下,我们需要确保各api的安全性,今天通过本文给大家分享Spring Cloud中如何保证各个微服务之间调用的安全性,需要的朋友参考下吧

导读:在微服务的架构下,系统会根据业务拆分为多个服务,各自负责单一的职责,在这样的架构下,我们需要确保各api的安全性,也就是说服务不是开放的,而是需要授权才可访问的,避免接口被不合法的请求所访问。

但是在在微服务集群中服务之间暴力的接口,或者对于第三方开放的接口如果不做及安全和认证,后果可想而知。

阅读下文之前思考几个问题:

  • 如何在restTemplate远程调用请求增加添加统一认证?
  • 服务认证如何规范加密和解密?
  • 远程调用统一什么协议比较合适?

如下图,三个服务注册到同一个注册中心集群,服务A、B、C之间如果不做任何限制,服务之间的接口基本是互通的。

教你Spring Cloud保证各个微服务之间调用安全性

但是如果A、B、C之间要做服务认证该如何设计?如果外部定制集成服务D接入怎么保证服务的安全性?

怎么加认证?

假设服务A是组织架构服务,服务B是规则引擎服务,服务C是公式引擎服务,如果B、C请求A服务调用用户信息除了开放接口规定参数。我们如何加入权限认证信息。

目前市面上主要两种方案处理

  • 请求体Body中加入参数校验 => SDK集成场景较多
  • 请求头Header中加入认证信息 token

请求头Header中加入认证信息 token,如下图结构所示。

教你Spring Cloud保证各个微服务之间调用安全性

确定服务认证统一在request header 加X-SERVICE-NAME,在服务服务中我们不可能每个服务都会主动去管理基础认证信息,仔细阅读RestTemplate源码不难发现,RestTemplate实现接口InterceptingHttpAccessor,因此我们可以再定义自己的拦截器来统一处理。

教你Spring Cloud保证各个微服务之间调用安全性

拦截@FeignClient 标识拦截

feign远程调用请求增加头部信息处理,重新定义 RequestInterceptor

public class FeignApiInterceptor implements RequestInterceptor {
 
    /**
     * 统一处理feign的远程调用拦截
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 远程调用请求增加头部信息处理(简写代码如下)
		requestTemplate.header("X-SERVICE-NAME", "...");
    }
}

拦截RestTemplate远程调用请求

RestTemplate 提供高度封装的接口,可以让我们非常方便地进行 Rest API 调用。常见的方法如下:

教你Spring Cloud保证各个微服务之间调用安全性

RestTemplate远程调用请求增加头部信息处理,统一处理处理restTemplate的请求拦截。

/**
 * restTemplate远程调用请求增加头部信息处理
 */
@Slf4j
public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor {
 
    /**
     * 处理restTemplate的请求拦截
     */
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        long startTime = System.currentTimeMillis();
        try {
            HttpHeaders headers = request.getHeaders();
            
            // 远程调用请求增加头部信息处理(简写代码如下)
			requestTemplate.header("X-SERVICE-NAME", "...");
            
            ClientHttpResponse response = execution.execute(request, body);
            
            // 加入链路深度Deep
            // ....
            return response;
        } finally {
            // do something
        }
    }
}

服务注册客户端配置

@Configuration
@EnableFeignClients(basePackages = NamingConstant.BASE_PACKAGE)
public class DiscoveryClientConfig {
    
    //......
    
    /**
     * feign请求拦截器
     */
    @Bean
    public RequestInterceptor feignInterceptor() {
        return new FeignApiInterceptor();
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(HttpClient httpClient,
                                     Environment environment) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplateBuilder.configure(restTemplate);
        restTemplate.setRequestFactory(buildFactory(httpClient, environment));
        // 加入自定义拦截器
        restTemplate.getInterceptors().add(new RestApiHeaderInterceptor());
        return restTemplate;
    }
    
}

拓展补充:

在处理RestTemplate的请求拦截的时候我们也可以追加链路追踪日志,具体对于链路日志的拓展可查阅《微服务分布式架构中,如何实现日志链路跟踪?》

简写代码

@Slf4j
public class RestApiHeaderInterceptor implements ClientHttpRequestInterceptor {
 
    /**
     * 处理restTemplate的请求拦截
     */
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        long startTime = System.currentTimeMillis();
        try {
            // 远程调用请求增加头部信息处理
            ClientHttpResponse response = execution.execute(request, body);
            // 获取传递线程变量
            Collection<String> values = response.getHeaders()
                    .get(TraceUtil.TRACE_RPCCOUNT);
            if (!CollectionUtils.isEmpty(values)) {
                int value = Integer.valueOf(values.iterator().next());
                // 链路深度追加
                TraceUtil.getRpcCounter().addAndGet(value + 1);
            }
            return response;
        } finally {
            // 是否开启链路追踪
            if (TraceUtil.isTraceLoggerOn()) {
                // 输出链路日志(接口耗时)
                TraceUtil.log(StringHelper.join("dt:", System.currentTimeMillis() - startTime,
                        ", TRACE-RPC-", request.getMethod(),
                        ", URI:", request.getURI()));
            }
        }
    }
}

怎么接收认证信息?

读过Spring-Web源码应该知道OncePerRequestFilter过滤器基类,旨在保证在任何 servlet 容器上每个请求分派一次执行。 它提供了一个带有 HttpServletRequest 和 HttpServletResponse 参数的doFilterInternal方法,详细用法可阅读源码。

另一种也出现在它自己的线程中的调度类型是ERROR 。 如果子类希望静态声明是否应该在错误调度期间调用一次,它们可以覆盖shouldNotFilterErrorDispatch() 。

getAlreadyFilteredAttributeName方法确定如何识别请求已被过滤。 默认实现基于具体过滤器实例的配置名称。

定义基础过滤器

public abstract class BaseWebFilter extends OncePerRequestFilter {
 
    /**
     * 返回类名,避免filter不被执行
     */
    @Override
    protected String getFilterName() {
        return null;
    }
}

过滤器定义虚拟类, 继承自接口的过滤器。如果声明为springbean,他自动加载到请求过滤链(因为继承了GenericFilterBean接口,spring默认把继承此类的过滤器bean加到web过滤链)中,通过注解@order定义其优先级如果不申明为springbean,又要加入到过滤链中,可以通过FilterRegistrationBean定义,并指定优先级

封装拦截 ApiValidationFilter

@Override
public BaseWebFilter getFilterInstance() {
    return new BaseWebFilter() {
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
            // 拦截校验
            if (validateToken(request)) {
                // 校验通过执行下一个lan"j
                chain.doFilter(request, response);
            } else {
                // 封装统一认证失败返回信息
                response.setStatus(401);
                response.setCharacterEncoding(
                    Charset.defaultCharset().displayName());
                response.setContentType(
                    MimeTypeUtils.APPLICATION_JSON.toString());
                response.getWriter().println(JsonUtil.toJsonString(Response
                                                                   .err("status.401")));
            }
        }
    };
}

针对与validateToken方法这里就不做展开,对于加密方式大同小异,根据自己项目情况来定。

private boolean validateToken(HttpServletRequest request) {
		if (!needValidate(request)) {
			return true;
		}
		String security = resolveToken(request);
		if (security == null) {
			log.info("验证信息缺失,请求地址:{}", request.getRequestURI());
			return false;
		}
		try {
            // 解析security加密规则
			return true;
		} catch (Exception e) {
			log.info("验证信息无效:{},请求地址:{}", security, request.getRequestURI());
			return false;
		}
	}

总结

回顾整个方案设计与实现,大致可分为以下几个步骤

参数加入位置:请求头Header还是请求体Body确定加入范围:@FeignClient客户端、构建RestTemplate以及集成服务远程接口调用确定拦截位置:Web线程拦截器,用于统一处理线程变量,该过滤器执行顺序早于springsecurity的过滤器确定加密/解密方式:加密字符串和解密​​​​​​

到此这篇关于教你Spring Cloud保证各个微服务之间调用安全性的文章就介绍到这了,更多相关Spring Cloud微服务调用内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/li1669852599/article/details/119803571

延伸 · 阅读

精彩推荐
  • Java教程Java通过CMD方式读取注册表任意键值对代码实践

    Java通过CMD方式读取注册表任意键值对代码实践

    这篇文章主要介绍了Java通过CMD方式读取注册表任意键值对代码实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    yaominghui7402019-06-26
  • Java教程MyBatis源码分析之日志logging详解

    MyBatis源码分析之日志logging详解

    这篇文章主要给大家介绍了关于MyBatis源码分析之日志logging的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价...

    魏晋秋9452021-07-18
  • Java教程解决SpringBoot跨域的三种方式

    解决SpringBoot跨域的三种方式

    前后端分离是目前的趋势,解决跨域问题也是老生常谈的话题了,我们了解一下什么是域和跨域。域:协议 + 域名 + 端口;三者完全相同则为同域,反之有...

    蚂蚁小哥5462021-09-16
  • Java教程java获取日期的方法

    java获取日期的方法

    这篇文章介绍了java获取日期的方法,有需要的朋友可以参考一下 ...

    java教程网4532019-10-17
  • Java教程kotlin改善java代码实例分析

    kotlin改善java代码实例分析

    我们给大家整理了关于kotlin改善java代码的相关实例以及操作的详细方法,有需要的读者们参考下。...

    codecraft10292021-04-18
  • Java教程java中实现四则运算代码

    java中实现四则运算代码

    本文给大家分享了几个java中实现四则运算的代码,有个人的也有网友的,代码写的不是很好,难免会有BUG,忘发现BUG的亲们能提醒我下,好让我改进 ...

    hebedich3152020-01-03
  • Java教程Java模拟栈和队列数据结构的基本示例讲解

    Java模拟栈和队列数据结构的基本示例讲解

    这篇文章主要介绍了Java模拟栈和队列数据结构的基本示例,栈的后进先出和队列的先进先出是数据结构中最基础的知识,本文则又对Java实现栈和队列结构的方...

    匆忙拥挤repeat4442020-04-17
  • Java教程java编程多线程并发处理实例解析

    java编程多线程并发处理实例解析

    这篇文章主要介绍了java编程多线程并发处理实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下...

    sherry_Rui7262021-03-25