Spring Cloud中如何保证各个微服务之间调用的安全性?

Spring Cloud中如何保证各个微服务之间调用的安全性?导读:在微服务的架构下,系统会根据业务拆分为多个服务,各自负责单一的职责,在这样的架构下,我们需要确保各api的安全性,也就是说服务不是开放的,

大家好,欢迎来到IT知识分享网。

导读:在微服务的架构下,系统会根据业务拆分为多个服务,各自负责单一的职责,在这样的架构下,我们需要确保各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的过滤器
  • 确定加密/解密方式:加密字符串和解密

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/72033.html

(0)
上一篇 2024-06-25 19:26
下一篇 2024-06-29 20:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信