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

2022-09-19  本文已影响0人  天草二十六_简村人

一、背景

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

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

Authentication

Key Auth

Security

Acl、Cors、Ip Restriction

Traffic Control

Rate Limiting

Analytics & Monitoring

Prometheus

kong插件.png

自定义插件

二、目标

三、服务调用整体框架

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

image.png

四、域名的管理

假定公司有三套环境,生产、测试和开发,对应的内网域名都是一样的,外网域名分别是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());

六、待补充的部分

Kong的自定义插件

上一篇 下一篇

猜你喜欢

热点阅读