美文网首页
说一说微服务网关下的服务调用

说一说微服务网关下的服务调用

作者: 天草二十六_简村人 | 来源:发表于2022-09-19 17:59 被阅读0次

一、背景

本文主要讲述微服务模式,API网关和后端服务的分工与协调。我们采用kong作为API网关,承担的是非功能性的工作,包括Token校验、cors跨域、接口开放、限流、监控、流量染色等。

Kong自带的有一些插件,详见下图,我们用到的主要有:

Authentication

Key Auth

Security

Acl、Cors、Ip Restriction

Traffic Control

Rate Limiting

Analytics & Monitoring

Prometheus

kong插件.png
  • Key Auth 在http header里传入api key字段,并且校验
  • Cors 解决前后端的跨域问题
  • Ip Restriction 基于IP的限流
  • Prometheus 采集调用次数与耗时的指标

自定义插件

  • auth 进行token校验、签名校验
  • Canary Release 灰度发布,用来对流量进行染色
  • Upstream 接口开放,按需配置,让你的接口更加安全
  • Rewrite 对接口的URI进行正则匹配,替换或截取

二、目标

  • 后端服务做到无状态的
  • 便于弹性伸缩
  • 提高接口的安全性

三、服务调用整体框架

这里以某个后端服务为例,梳理了内外网、服务之间的调用链路。

image.png
  • 内网调用
    要么走内网域名,要么通过consul服务发现。 当然这里也涉及到内部的调用安全问题,暂时未纳入考虑范围。
    内网网关也选择Kong的原因是需要对内网流量进行染色,适用于灰度发布。

  • 外网调用
    前端调用,可能是没有token或签名,还需要解决跨域问题(建议将资源都发布到oss,一次性做好跨域处理)
    端的调用,一般都是需要token和签名的,会由Kong去调用认证服务,校验合法性,并返回userId,透传到后端服务。

  • 角色权限
    除了token的合法性外,还有userId和token是否一致、用户的角色是否能够访问某个接口、不同的角色能够访问的资源不同(需要做数据的过滤或区分)。
    目前平台是把权限这块下沉到具体的服务自己实现。所有后文,我会总结下如何简单实现。

  • 应用监控
    采用ip + port的方式,适用于所有的可观测性指标的采集。

  • 接口测试
    建议使用内网的ip+port的方式,当然你还需要对内网域名和外网域名下的访问做回归测试,甚至是不同的客户端应用版本,也需要做回归测试。

四、域名的管理

假定公司有三套环境,生产、测试和开发,对应的内网域名都是一样的,外网域名分别是xxx.net / xxx.test.com / xxx.dev.com。

内网域名进来的请求,都不需要token校验,但还是需要解决跨域问题,上图这一点没有全部描述出来。

外网域名进来的请求,除了登录接口、获取短信验证码等不需要token校验外,都是需要由kong做请求拦截的。

当然,能够走consul服务发现的服务之间调用,尽量走consul了。

五、权限的简单实现

编写自己的自定义注解,并在接口层使用该注解;拦截器中扫描到自定义的注解,将http header透传下来的userId,查询它的权限及角色; 进行权限的校验,并将用户信息保存在上下文里。

5.1、自定义注解

/**
 * 权限限制.
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {

    /**
     * 权限校验(默认true)
     */
    boolean limit() default true;

    /**
     * 要求的权限标签列表
     *
     * @return
     */
    AuthorityEnum[] authorityTagSet() default {AuthorityEnum.ADMIN};

}

5.2、引用注解

    @PermissionLimit(authorityTagSet = {AuthorityEnum.MANAGER, AuthorityEnum.ADMIN})

5.3、角色的枚举

import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;

/**
 * 权限枚举
 *
 * @author Administrator
 */
public enum AuthorityEnum {
    /**
     * 超级管理员
     */
    ADMIN("admin", "超级管理员"),
    /**
     * 管理员
     */
    MANAGER("auditor", "审核员"),
    /**
     * 用户
     */
    USER("user", "用户");

    private String code;

    private String name;

    AuthorityEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }


    public String getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    public static String getName(String code) {
        return CODE_MAP.containsKey(code) ? CODE_MAP.get(code).getName() : null;
    }

    private static final Map<String, AuthorityEnum> CODE_MAP =
            Collections.unmodifiableMap(Arrays.stream(values()).collect(toMap(AuthorityEnum::getCode, Function.identity(), (v1, v2) -> v2)));

    public static AuthorityEnum of(String ordinal) {
        if(StringUtils.isEmpty(ordinal) || !CODE_MAP.containsKey(ordinal)){
            return USER;
        }
        return CODE_MAP.get(ordinal);
    }

}

5.4、权限拦截器

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 权限拦截
 *
 * @author zhuwenping
 */
@Slf4j
@Component
public class PermissionInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return super.preHandle(request, response, handler);
        }

        HandlerMethod method = (HandlerMethod) handler;
        PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
        if (Objects.nonNull(permission)) {
            //0、是否开启校验权限,如果否,则跳过此步骤。
            if (!permission.limit()) {
                return super.preHandle(request, response, handler);
            }

            AuthorityEnum[] authorityTagArray = permission.authorityTagSet();
            Set<AuthorityEnum> authorityTagSet = Arrays.stream(authorityTagArray).collect(Collectors.toSet());

            //1、从token中解析出当前登录用户的userId
            String authUserIdStr = request.getHeader(JwtAuthHeaders.AUTH_USER_ID);
            Precondition.isTrue(StrUtil.isNotBlank(authUserIdStr), "用户未登录");
            Precondition.isTrue(NumberUtil.isNumber(authUserIdStr), "无效的用户ID");

            //2、查询用户的权限标签
            UserDTO userDTO = userService.getUser(Long.parseLong(authUserIdStr));
            Precondition.isTrue(Objects.nonNull(userDTO), "用户不存在");
            if (CollectionUtil.isNotEmpty(authorityTagSet)) {
                Precondition.isTrue(authorityTagSet.contains(AuthorityEnum.of(userDTO.getAuthorityTag())), "用户的权限不足");
            }

            //3、把用户的权限保存到线程上下文
            UserAuthorityThreadLocal.setAuthority(AuthorityEnum.of(userDTO.getAuthorityTag()));
        }

        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("进入到拦截器中:afterCompletion() 方法中");
        // remove线程上下文中的用户权限
        UserAuthorityThreadLocal.remove();
    }
}

5.5、用户信息的上下文

/**
 * 用户权限保存至线程上下文.
 *
 */
public class UserAuthorityThreadLocal {

    static InheritableThreadLocal<AuthorityEnum> authorityContext = new InheritableThreadLocal<AuthorityEnum>() {
        @Override
        protected AuthorityEnum initialValue() {
            return super.initialValue();
        }
    };

    public static void setAuthority(AuthorityEnum authority) {
        authorityContext.set(authority);
    }

    public static void remove() {
        authorityContext.remove();
    }

    public static AuthorityEnum getAuthority() {
        return authorityContext.get();
    }
}

5.6、使用示例

//1、if/else判断,程序走向不同的逻辑
if (AuthorityEnum.ADMIN.equals(UserAuthorityThreadLocal.getAuthority()) || AuthorityEnum.MANAGER.equals(UserAuthorityThreadLocal.getAuthority())) {
 //
 } else {
  //
 }

//2、流计算中作filter数据过滤
List<QueryQuestionRes> sortedList = randomList.stream()
                    .filter(q -> AuthorityEnum.ADMIN.equals(UserAuthorityThreadLocal.getAuthority()))
                    .collect(Collectors.toList());

六、待补充的部分

  • 灰度发布,流量染色
  • http header中的userId透传
  • userId和token是否一致的问题
  • kong的自定义插件

Kong的自定义插件

  • kong的可观测性,除了Prometheus能够采集的指标外,我们还开发了打印日志功能,将一些异常情况,输出到指定的文件,然后上报到ELK。
  • Kong的upsteam中的target需要和consul中的服务节点保持一致,做到实时地动态更新,减少运维过程中带来的不必要的错误。建议在发布过程中,调用kong 的 api接口,添加或删除target。 这样,你监控了consul中的服务节点,如果是健康的,也就可以保证upstream的target节点也是健康无误。
  • 流量染色插件,这里的配套实现离不开java agent技术。

相关文章

  • 说一说微服务网关下的服务调用

    一、背景 本文主要讲述微服务模式,API网关和后端服务的分工与协调。我们采用kong作为API网关,承担的是非功能...

  • 微服务 六:服务网关

    什么是服务网关、为什么需要服务网关 服务除了内部相互之间调用和通信之外,最终要以某种方式暴露出去,才能让外界系统(...

  • 2020 技术趋势个人整合

    微服务框架技术 服务注册与发现 服务调用 服务熔断 负载均衡 服务降级 服务消息队列 配置中心管理 服务网关 服务...

  • 2018-03-27

    微服务网关 什么是微服务网关 微服务网关也称为服务网关或者API网关。服务网关 = 路由转发 + 过滤器...

  • Consul 快速入门 - Kong最佳实践

    Consul是什么 Consul是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控...

  • spring cloud微服务体系

    微服务涉及组件 服务注册与发现 服务调用 服务网关 服务降级熔断 服务配置中心和服务总线 中间件消息驱动 链路追踪...

  • gateway hystrix超时熔断

    配置 定义熔断后fallback() 这种熔断只能针对服务网关调用后端服务接口超时才会生效,后端服务抛异常无效

  • 为什么需要后端网关

    一、什么是服务网关 二、为什么需要服务网关 三、服务网关技术选型 1、总体流程 2、引入网关的注意点 3、服务网关...

  • istio是什么?怎么搭建?

    服务网格(service mesh) 首先引入服务网格的概念,服务网格就是service mesh ,在很多年前就...

  • Openshift之服务网格Istio

    什么是服务网格? 服务网络就是指构成应用程序的微服务网络以及应用之间的交互。随着规模和复杂性的增长,服务网格越来越...

网友评论

      本文标题:说一说微服务网关下的服务调用

      本文链接:https://www.haomeiwen.com/subject/tdmportx.html