Spring Cloud Alibaba系列之-Spring Cloud Gateway网关权限(六)

一、功能说明

认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。

具体服务:
[gateway-service]:网关服务,负责请求转发和鉴权功能,整合JWT;
[auth-service]:认证服务,负责对登录用户进行认证,整合JWT;
[user-service]:受保护的API服务,用户鉴权通过后可以访问该服务,不整合JWT;

架构图如下:


架构图

二、授权服务module[auth-service]

2.1 新建module[auth-service]

1、选中ac-mall-cloud项目,选择新建module

新建module

2、选择maven工程

选择maven工程

3、填写module名称

填写module名称

4、完成效果

完成效果
2.2 maven配置

引入redis、jwt等依赖,module [auth-service]pom.xml全配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.ac</groupId>
    <artifactId>auth-service</artifactId>

    <properties>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        <alibaba.cloud.version>2.1.0.RELEASE</alibaba.cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${alibaba.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

    </dependencies>
</project>
2.3 创建启动类AuthServiceApplication

新建com.ac.auth 包,并创建SpringBoot启动类AuthServiceApplication

@EnableDiscoveryClient
@SpringBootApplication
public class AuthServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}
2.4 新建application.yml配置文件

1、配置启动端口6010
2、配置Nacos服务注册中心
3、配置Redis

server:
  port: 6010

spring:
  application:
    name: auth-service

  cloud:
    nacos:
      discovery:
        server-addr: 47.105.146.74:8848

  redis:
    database: 0
    host: 39.108.250.186
    port: 6379
    password: xxxxx
    jedis:
      pool:
        max-active: 500  #连接池的最大数据库连接数。设为0表示无限制
        max-idle: 20   #最大空闲数
        max-wait: -1
        min-idle: 5
    timeout: 1000
2.5 token授权代码

新建com.ac.auth.util 包,并创建JWTUtil类

import com.ac.auth.response.ResponseCodeEnum;
import com.ac.auth.response.TokenAuthenticationException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;

public class JWTUtil {

    public static final long TOKEN_EXPIRE_TIME = 7200 * 1000;
    private static final String ISSUER = "ac";

    public static String generateToken(String userId,String username, String secretKey) {
        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        Date now = new Date();
        Date expireTime = new Date(now.getTime() + TOKEN_EXPIRE_TIME);

        String token = JWT.create()
                .withIssuer(ISSUER)
                .withIssuedAt(now)
                .withExpiresAt(expireTime)
                .withClaim("userId", userId)
                .withClaim("username", username)
                .sign(algorithm);

        return token;
    }

    public static void verifyToken(String token, String secretKey) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWTVerifier jwtVerifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            jwtVerifier.verify(token);
        } catch (JWTDecodeException jwtDecodeException) {
            throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_INVALID.getCode(), ResponseCodeEnum.TOKEN_INVALID.getMessage());
        } catch (SignatureVerificationException signatureVerificationException) {
            throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_SIGNATURE_INVALID.getCode(), ResponseCodeEnum.TOKEN_SIGNATURE_INVALID.getMessage());
        } catch (TokenExpiredException tokenExpiredException) {
            throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_EXPIRED.getCode(), ResponseCodeEnum.TOKEN_INVALID.getMessage());
        } catch (Exception ex) {
            throw new TokenAuthenticationException(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), ResponseCodeEnum.UNKNOWN_ERROR.getMessage());
        }
    }

    public static String getUserName(String token) {
        try{
            DecodedJWT decodedJWT = JWT.decode(token);
            String username = decodedJWT.getClaim("username").asString();
            return username;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public static String getUserId(String token) {
        try {
            DecodedJWT decodedJWT = JWT.decode(token);
            String userId = decodedJWT.getClaim("userId").asString();
            return userId;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

新建com.ac.auth.response 包,并创建LoginResponse、ResponseCodeEnum、ResponseResult、TokenAuthenticationException类

@Data
public class LoginResponse {

    private String token;

    private String refreshToken;

    private String userId;

    private String username;
}
public enum ResponseCodeEnum {
    SUCCESS(0, "成功"),

    FAIL(-1, "失败"),

    LOGIN_ERROR(1000, "用户名或密码错误"),

    UNKNOWN_ERROR(2000, "未知错误"),

    PARAMETER_ILLEGAL(2001, "参数不合法"),

    TOKEN_INVALID(2002, "无效的Token"),

    TOKEN_SIGNATURE_INVALID(2003, "无效的签名"),

    TOKEN_EXPIRED(2004, "token已过期"),

    TOKEN_MISSION(2005, "token缺失"),

    REFRESH_TOKEN_INVALID(2006, "刷新Token无效");

    private int code;

    private String message;

    ResponseCodeEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}
public class ResponseResult<T> {

    private int code = 0;

    private String msg;

    private T data;

    public ResponseResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static ResponseResult success() {
        return new ResponseResult(ResponseCodeEnum.SUCCESS.getCode(), ResponseCodeEnum.SUCCESS.getMessage());
    }

    public static <T> ResponseResult<T> success(T data) {
        return new ResponseResult(ResponseCodeEnum.SUCCESS.getCode(), ResponseCodeEnum.SUCCESS.getMessage(), data);
    }

    public static ResponseResult error(int code, String msg) {
        return new ResponseResult(code, msg);
    }

    public static <T> ResponseResult<T> error(int code, String msg, T data) {
        return new ResponseResult(code, msg, data);
    }

    public boolean isSuccess() {
        return code == 0;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
public class TokenAuthenticationException extends RuntimeException {

    public TokenAuthenticationException() {
        super();
    }

    public TokenAuthenticationException(int code, String message) {
        super(code + message);
    }
}

新建com.ac.auth.request包,并创建LoginRequest、RefreshRequest类

@Data
public class LoginRequest {

    private String username;

    private String password;
}
@Data
public class RefreshRequest {

    private String userId;

    private String refreshToken;
}

新建com.ac.auth.controller包,并创建LoginController类

import com.ac.auth.request.LoginRequest;
import com.ac.auth.request.RefreshRequest;
import com.ac.auth.response.LoginResponse;
import com.ac.auth.response.ResponseCodeEnum;
import com.ac.auth.response.ResponseResult;
import com.ac.auth.util.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/auth")
public class LoginController {

    @Value("${secretKey:123456}")
    private String secretKey;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    final static String TOKEN = "token";

    final static String REFRESH_TOKEN = "refreshToken";

    @PostMapping("/login")
    public ResponseResult login(@RequestBody @Validated LoginRequest request, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResponseResult.error(ResponseCodeEnum.PARAMETER_ILLEGAL.getCode(), ResponseCodeEnum.PARAMETER_ILLEGAL.getMessage());
        }

        String username = request.getUsername();
        String password = request.getPassword();
        //  假设查询到用户ID是1001
        String userId = "1001";
        if ("alanchen".equals(username) && "admin".equals(password)) {
            //  生成Token
            String token = JWTUtil.generateToken(userId,username,secretKey);

            //  生成刷新Token
            String refreshToken = UUID.randomUUID().toString().replace("-", "");

            //  放入缓存
            HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();

            String key = userId;
            hashOperations.put(key, TOKEN, token);
            hashOperations.put(key, REFRESH_TOKEN, refreshToken);
            stringRedisTemplate.expire(key, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);

            LoginResponse loginResponse = new LoginResponse();
            loginResponse.setToken(token);
            loginResponse.setRefreshToken(refreshToken);
            loginResponse.setUserId(userId);
            loginResponse.setUsername(username);

            return ResponseResult.success(loginResponse);
        }

        return ResponseResult.error(ResponseCodeEnum.LOGIN_ERROR.getCode(), ResponseCodeEnum.LOGIN_ERROR.getMessage());
    }

    @GetMapping("/logout")
    public ResponseResult logout(@RequestParam("userId") String userId) {
        HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
        String key = userId;
        hashOperations.delete(key, TOKEN);
        return ResponseResult.success();
    }

    @PostMapping("/refreshToken")
    public ResponseResult refreshToken(@RequestBody @Validated RefreshRequest request, BindingResult bindingResult) {
        String userId = request.getUserId();
        //通过userId去数据库查到userName
        String userName="alanchen";
        String refreshToken = request.getRefreshToken();
        HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
        String key = userId;
        String originalRefreshToken = hashOperations.get(key, REFRESH_TOKEN);
        if (StringUtils.isBlank(originalRefreshToken) || !originalRefreshToken.equals(refreshToken)) {
            return ResponseResult.error(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(), ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage());
        }

        //  生成新token
        String newToken = JWTUtil.generateToken(userId,userName,secretKey);
        hashOperations.put(key, TOKEN, newToken);
        stringRedisTemplate.expire(userId, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);

        return ResponseResult.success(newToken);
    }
}
2.6 项目结构
auth-service项目结构

三、网关服务module[gateway-service]

我们需要在网关服务module[gateway-service]中加入权限校验代码,客户端通过网关访问微服务前,都必须先通过网关服务的鉴权。

3.1 maven配置

添加redis、jwt等依赖,module [gateway-service]pom.xml全配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.ac</groupId>
    <artifactId>gateway-service</artifactId>

    <properties>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        <alibaba.cloud.version>2.1.0.RELEASE</alibaba.cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${alibaba.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

    </dependencies>

</project>
3.2 添加[auth-service]服务网关路由配置

在[gateway-service]application.yml中添加[auth-service]服务网关路由配置,如下

- id: auth-service-api
  uri: lb://auth-service
  predicates:
    - Path=/auth/**

[gateway-service]application.yml 完整配置如下

server:
  port: 7010

spring:
  application:
    name: gateway-service

  redis:
    database: 0
    host: 39.108.250.186
    port: 6379
    password: xxxxx
    jedis:
      pool:
        max-active: 500  #连接池的最大数据库连接数。设为0表示无限制
        max-idle: 20   #最大空闲数
        max-wait: -1
        min-idle: 5
    timeout: 1000

  cloud:
    nacos:
      discovery:
        server-addr: 47.105.146.74:8848

    gateway:
      routes:                       # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: user-service-api          # 当前路由的标识, 要求唯一
          uri: lb://user-service  # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
          predicates:                # 断言(就是路由转发要满足的条件)
            - Path=/users/**       # 当请求路径满足Path指定的规则时,才进行路由转发
            #filters:                   # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            #- StripPrefix=1           # 转发之前去掉1层路径

        - id: order-service-api
          uri: lb://order-service
          predicates:
            - Path=/orders/**

        - id: auth-service-api
          uri: lb://auth-service
          predicates:
            - Path=/auth/**

        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq
3.3 加入token相关代码

因为鉴权时也需要用token,因此[gateway-service]同样需要token相关的代码,我们可以将[auth-service]服务中response包和util 包下的代码直接复制过来。如果觉得代码重复,也可以新建一个权限相关的公共module,让后让[auth-service]、[gateway-service]都依赖它。也可以将这些重复的代码打成一个jar包,让[auth-service]、[gateway-service]引入。

image.png
3.4 权限(token)校验代码

新建auth 包,创建TokenFilter类

import com.ac.gateway.response.ResponseCodeEnum;
import com.ac.gateway.response.ResponseResult;
import com.ac.gateway.response.TokenAuthenticationException;
import com.ac.gateway.util.JWTUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Value("${secretKey:123456}")
    private String secretKey;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    final static String TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        String uri = serverHttpRequest.getURI().getPath();

        //  检查白名单(配置)
        if (uri.indexOf("/auth/login") >= 0) {
            return chain.filter(exchange);
        }

        String token = serverHttpRequest.getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(token)) {
            serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
            return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_MISSION);
        }

        String userId = JWTUtil.getUserId(token);
        if(userId == null){
            return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
        }

        // 检查Redis中是否有此Token(退出登录有删除token)
        HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
        String redisToken = hashOperations.get(userId, TOKEN);
        if (!token.equals(redisToken)) {
            return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
        }

        try {
            JWTUtil.verifyToken(token, secretKey);
        } catch (TokenAuthenticationException ex) {
            return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
        } catch (Exception ex) {
            return getVoidMono(serverHttpResponse, ResponseCodeEnum.UNKNOWN_ERROR);
        }


        ServerHttpRequest mutableReq = serverHttpRequest.mutate().header("userId", userId).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();

        return chain.filter(mutableExchange);

    }

    private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ResponseCodeEnum responseCodeEnum) {
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        ResponseResult responseResult = ResponseResult.error(responseCodeEnum.getCode(), responseCodeEnum.getMessage());
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSON.toJSONString(responseResult).getBytes());
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

四、测试

1、依次启动[user-service]、[auth-service]、[gateway-service]

2、先直接访问[user-service]的获取用户接口是否正常


获取用户信息接口

3、通过[gateway-service]访问[user-service]的获取用户接口


访问失败

权限校验起效果了,由于没有传合法的token,因此访问接口失败

4、通过[gateway-service]访问[auth-service],获取合法的token


授权token

5、加上token,重新通过[gateway-service]访问[user-service]的获取用户接口


image.png

五、问题补充

5.1 怎么防止客户端直接访问微服务

我们都知道网关适合做认证和鉴权,但是在安全层面,我们要求更严格的权限,对于有些项目来说,本身网络跟外部隔离,再加上其它的安全手段,所以我们只要求在网关上鉴权就可以了。

具体来说,微服务[user-service]所在服务器设置防火墙限定IP和端口只能由[gateway-service]来访问,客户端所有请求,都先通过[gateway-service]的权限校验才能转发访问到[user-service]接口

5.2 服务与服务之前的权限怎么控制?

一般情况下,服务与服务之间可以直接调用,不需要加权限控制。但有些项目权限控制要求比较高,要求服务对服务之间的调用进行鉴权,知道某个用户是否有权限调用某个接口,这些都需要进行鉴权,这时的方案如下。

1、在Gateway网关层做认证,通过用户校验后,传递用户信息到Header中,后台服务在收到Header后进行解析,解析完后查看是否有调用此服务或者某个url的权限,然后完成鉴权。

2、从服务内部发出的请求,在出去时进行拦截,把用户信息保存在Header里,然后传出去,被调用方取到Header后进行解析和鉴权。

六、附录

项目源码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容