GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现

Posted Roninaxious

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现相关的知识,希望对你有一定的参考价值。


github地址:https://github.com/Zealon159/light-reading-cloud


该网关层面使用Spring GateWay进行实现。

1.配置文件精解

(1)bootstrap.yml文件

spring:
  application:
    # 服务逻辑名称
    name: light-reading-cloud-gateway

  cloud:
    nacos:
      # 配置中心
      config:
        server-addr: xxxxxx
        file-extension: yml
        refresh: true
        shared-dataids: light-reading-cloud-gateway.yml  #Data ID
        namespace: 4d109a4d-f34d-4e86-9e39-c2d36db24b00  #命名空间

      # 注册中心
      discovery:
        server-addr: xxxxx
        namespace: 4d109a4d-f34d-4e86-9e39-c2d36db24b00

(2)nacos中关于gateway的配置信息

server:
  port: 8010

spring:
  application:
    # 服务逻辑名称
    name: light-reading-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true   #开启gateway的注册和发现
          lowerCaseServiceId: true	#将请求路径中的服务名小写,因为服务注册时,Nacos将其转换成大写了
      routes:   #路由匹配
        - id: book-center-rpc			
          uri: lb://light-reading-cloud-book  #服务地址
          predicates:   #断言,匹配路径和请求方式
            - Path=/book/**
            - Method=GET
          filters:
            # 降级配置
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback #失败了直接跳转到该路据,这个接口是用户失败后快速返回的
            # 限流配置
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 3 # 每秒允许处理的请求数量
                redis-rate-limiter.burstCapacity: 5 # 每秒最大处理的请求数量
                key-resolver: "#{@ipKeyResolver}" # 限流策略,对应策略的Bean,在gateway配置了该IP的限流策略


        - id: homepage-rpc
          uri: lb://light-reading-cloud-homepage
          predicates:
            - Path=/index/**
          filters:
            # 降级配置
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback
            # 限流配置
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 3
                redis-rate-limiter.burstCapacity: 5
                key-resolver: "#{@ipKeyResolver}"

        - id: account-center-rpc
          uri: lb://light-reading-cloud-account
          predicates:
            - Path=/account/**
          filters:
            # 降级配置
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback
            # 限流配置
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 3
                redis-rate-limiter.burstCapacity: 5
                key-resolver: "#{@ipKeyResolver}"

hystrix:
  threadpool:
    default:
      coreSize: 20 #并发执行的最大线程数,默认10
      maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
      queueSizeRejectionThreshold: 400
ribbon:
  eager-load:
    enabled: true  #开启Ribbon的饥饿模式,  用于点对点直连问题
    clients: light-reading-cloud-account,light-reading-cloud-book,light-reading-cloud-homepage

  

(3)applicaton.properties白名单配置

system.properties=/account/user/register,/account/user/login

这个本地application.properties是配置的白名单信息,后面会在配置类中进行加载。

2.代码详解

(1)IP限流

@Configuration
public class RedisRateLimiterConfig {

    /**
     * 按客户端IP限流
     * Lambda表达式或者匿名函数都可以快速实现
     * @return
     */
    @Bean
    public KeyResolver ipKeyResolver() {  
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}

这个ipKeyResolver方法是在gateway配置文件中引用的

(2)白名单

@Data
@Component
public class SystemPropertiesConfig {
    /** 请求白名单,引用application.preperties中的属性 */
    @Value("${system.properties}")
    private String whitelist;
}

(3)Jwt工具类验证token的有效性

Token比较适用于微服务的安全认证,JWT是一种安全认证规范,token中存储了用户信息,只有在服务端才能根据密钥进行解密。

public class JwtUtil {

    /**
     * 身份认证
     * @param jwt 令牌
     * @return 成功状态返回200,其它均为失败
     */
    public static Result<User> validationToken(String jwt) {
        try {
            //解析JWT字符串中的数据,并进行最基础的验证
            Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(jwt)
                .getBody();
            //如果解析成功,将其封装到User返回
            User user = new User();
            user.setUuid(claims.get("uuid").toString());
            user.setLoginName(claims.get("loginName").toString());
            user.setNickName(claims.get("nickName").toString());
            if (claims.get("phoneNumber") != null) {
                user.setPhoneNumber(claims.get("phoneNumber").toString());
            }
            user.setId(Integer.parseInt(claims.get("id").toString()));
            user.setHeadImgUrl(claims.get("headImgUrl").toString());
            return ResultUtil.success(user);
        } catch (ExpiredJwtException e) {
            // 已过期令牌
            return ResultUtil.authExpired();
        } catch (SignatureException e) {
            // 伪造令牌
            return ResultUtil.unAuthorized();
        } catch (Exception e) {
            // 系统错误
            return ResultUtil.unAuthorized();
        }
    }
    
}

(4)网关层面快速失败返回接口

public class FallbackController {

    @GetMapping("/fallback")
    public Result fallback() {
        return ResultUtil.fail();
    }
}

(5)微服务安全认证流程

常用的认证方式主要有三种:Session、HTTP Basic Authentication 和 Token。

  • session 是认证中最常用的一种方式,也是最简单的。用户登录后将信息存储在后端,客户端则通过 Cookie 中的 SessionId 来标识对应的用户。
  • HTTP Basic Authentication 也就是 HTTP 基本认证,它是 HTTP 1.0 提出的一种认证机制。HTTP 基本认证的原理是客户端在请求时会在请求头中增加 Authorization,Authorization 是用户名和密码用 Base64 加密后的内容。服务端获取 Authorization Header 中的用户名与密码进行验证。
  • Token 中会存储用户的信息,然后通过加密算法进行加密,只有服务端才能解密,服务端拿到 Token 后进行解密获取用户信息。

Token更适用于微服务的安全认证,本项目采用了token这种认证方式,并基于JWT的安全认证规范。

jwt是一种认证规范,它允许我们通过jwt在用户和服务器之间传递可靠的信息。在通信过程中进行身份认证

Ⅰ.用户进行登录时,将用户名和密码提交给认证服务器,服务器会验证用户提交信息的合法性,如果验证成功,则会返回一个token,客户端将token保存起来
Ⅱ.用户再次请求服务器时,一般会将token放到请求头中。当请求达到网关后,会在网关中对token进行校验;如果校验成功,网关会将其转发到后端服务中,转发时会将用户信息一并传递过去,这样后端服务就不用再进行校验了。

(1)网关是唯一的入口,所以微服务之间的请求就不需要再进行认证。
(2)有些请求是不需要进行认证的,所以我们加入了白名单进行处理。
(3)jwt的认证过程主要是加密,而加密会耗费CPU的运算资源。如果请求量过大,可以将token缓存起来,这样可以提高网关服务器CPU的性能。

(6)身份认证过滤器实现

对于认证过滤器实现类GlobalFilter和Ordered接口
1.GlobalFilter是gateway中的一个全局过滤器
2.我们知道gateway中的核心由一个过滤器链组成,这个过滤器链中的一个个过滤器是由Ordered进行排序的,数值越小越靠前执行。
3.ServerWebExchange是gateway中的一个网络交换器,它的内部封装了HTTP请求信息和响应信息,我们可以在Filter中根据这个ServerWebExchange对请求信息或者响应信息进行拦截,然后进行响应的操作。
4.consumer是Java8提供了一个消费性函数式接口,对此我们可以通过lambda快速向请求头中加入解析后的用户信息
5.一般情况下,请求和响应中的信息是不能修改的,但gateway为我们提供类一个mutate方法,专门用来修改请求和响应信息。由于我们通过ServerWebExchange进行数据交换的,所以我们可以先将信息追加到ServerHttpRequest中,然后再将ServerHttpRequest封装到ServerWebExchange中。

/**
 * 身份认证过滤器
 * @author: Ronin
 * @since: 2021/10/5
 * @email:
 * 统一认证的实现方式是自定义实现全局过滤器,在过滤器里面可以处理白名单放行、认证校验、动态处理请求参数等
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired  //白名单配置类
    private SystemPropertiesConfig systemPropertiesConfig;

    @Override   
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 白名单Path,获取白名单,获取配置文件中的白名单
        Set<String> whiteList = this.getWhiteList();
        //获取请求信息中的请求路径
        String path = exchange.getRequest().getPath().toString();

        // 主页接口、图书接口正则匹配,如果匹配这两个表示不用登录就可以访问
        boolean indexMatch = Pattern.matches("/index[^\\\\s]*", path);
        boolean bookMatch = Pattern.matches("/book/[^\\\\s]*", path);

        // 白名单接口、开放接口放行
        if (whiteList.contains(path) || bookMatch || indexMatch) {
            return chain.filter(exchange);
        }

        String[] segments = path.split("/");
        if (!whiteList.contains(segments[1])) {
            // 认证--从请求头中获取token,通过jwt进行校验
            String token = exchange.getRequest().getHeaders().getFirst("token");
            Result<User> result = JwtUtil.validationToken(token);
            if (result.getCode() == HttpCodeEnum.OK.getCode()) {
                // 认证通过
                User user = result.getData();
                // 追加请求头用户信息
                Consumer<HttpHeaders> httpHeaders = httpHeader -> {
                    httpHeader.set("userId",user.getId().toString());
                    httpHeader.set("nickName",user.getNickName());
                };
                ServerHttpRequest serverHttpRequest = exchange.getRequest()
                        .mutate()
                        .headers(httpHeaders)
                        .build();
                exchange.mutate().request(serverHttpRequest).build();
                return chain.filter(exchange);
            }
            // 认证过期、失败,均返回401
            ServerHttpResponse response = exchange.getResponse();
            //将对象转换为byte  - json字符串
            byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 指定编码,否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;  //数值越小,该过滤器越向前执行
    }

    /**
     * 请求白名单
     * @return
     */
    private Set<String> getWhiteList(){
        String whitelists = this.systemPropertiesConfig.getWhitelist();
        if (StringUtils.isEmpty(whitelists)) {
            return new HashSet<>();
        }
        Set<String> whiteList = new HashSet<>();
        String[] whiteArray = whitelists.split(",");
        for (int i = 0; i < whiteArray.length; i++) {
            whiteList.add(whiteArray[i]);
        }
        return whiteList;
    }
}

以上是关于GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现的主要内容,如果未能解决你的问题,请参考以下文章

GitHub轻松阅读微服务实战项目流程详解第四天:账户服务的设计与实现

GitHub轻松阅读微服务实战项目流程详解第三天:公共模块的设计与实现

Spring Cloud 微服务实战

Spring Cloud微服务实战

SpringCloud微服务实战学习系列配置详解

Docker详解与部署微服务实战