电商(二)网关微服务搭建

文章目录

  • 一、微服务网关
    • (一)微服务网关的概述
    • (二)微服务网关技术
  • 二、网关搭建
      • 1.引入依赖
      • 2.创建 shop-gateway-web工程
        • (1)pom.xml:
        • (2)启动类
        • (3)application.yml跨域配置
        • (4)网关过滤配置
          • 4.1 Host 路由
          • 4.1 动态路由(按照服务名转发)
          • 4.2 路径匹配过滤配置
          • 4.3 PrefixPath 过滤配置
          • 4.4 StripPrefix 过滤配置
          • 4.5 LoadBalancerClient 路由过滤器(客户端负载均衡)
        • (5)网关限流
          • 5.1 令牌桶算法
          • 5.2 使用令牌桶进行请求次数限流
            • (1)引入redis依赖
            • (2) 定义KeyResolver
            • (3)修改application.yml中配置项,指定限制流量的配置以及REDIS的配置
      • 3.用户登录
        • (1)表结构介绍 用户信息user表
        • (2)用户微服务创建
          • 2.1 在shop-service-api中创建shop-service-user-api,并将pojo拷贝到工程中
          • 2.2 在shop-service中创建shop-service-user微服务,并引入生成的业务逻辑代码
          • 2.3 在shop-service-user的pom.xml引入如下依赖:
          • 2.4 启动类创建
          • 2.5 application.yml配置
          • 2.6 登录
          • 2.7 网关关联
      • 4.鉴权处理
        • (1) 思路分析
        • (2) 用户登录签发TOKEN
          • 2.1生成令牌工具类
        • (3) 用户登录成功 则 签发TOKEN,修改登录的方法:
        • (4) 网关过滤器拦截请求处理
        • (5) 自定义全局过滤器
        • (6) 配置过滤规则
      • 5 会话保持
        • (1) 登录封装Cookie
        • (2)过滤器获取令牌数据
        • (3) 添加Header信息


一、微服务网关

(一)微服务网关的概述

不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

客户端会多次请求不同的微服务,增加了客户端的复杂性
存在跨域请求,在一定场景下处理相对复杂
认证复杂,每个服务都需要独立认证
难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施
某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难
以上这些问题可以借助网关解决。
网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 网关来做,这样既提高业务灵活性又不缺安全性.
优点如下:

安全 ,只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
减少了客户端与各个微服务之间的交互次数
易于统一授权。
总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。

(二)微服务网关技术

实现微服务网关的技术有很多,

nginx Nginx (tengine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务
zuul ,Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
spring-cloud-gateway, 是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。
我们使用gateway这个网关技术,无缝衔接到基于spring cloud的微服务开发中来。

gateway官网:https://spring.io/projects/spring-cloud-gateway

二、网关搭建

shop-gateway模块搭建
电商(二)网关微服务搭建_第1张图片

1.引入依赖

修改changgou-gateway工程,打包方式为pom
pom.xml如下:


<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>
        <artifactId>shop-parentartifactId>
        <groupId>com.bubagroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>shop-gatewayartifactId>
    <packaging>pompackaging>
    <properties>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
    properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>
project>

2.创建 shop-gateway-web工程

在shop-gateway工程中,创建 shop-gateway-web工程,该网关主要用于对后台微服务进行一个调用操作,将多个微服务串联到一起。
电商(二)网关微服务搭建_第2张图片

(1)pom.xml:


<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>
        <artifactId>shop-gatewayartifactId>
        <groupId>com.bubagroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>shop-gateway-webartifactId>

    <properties>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
    properties>
    <description>
        普通web请求网关
    description>
project>

(2)启动类

在shop-gateway-web中创建一个引导类com.buba.GatewayWebApplication,代码如下:

package com.buba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author qlx
 */
@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication{
    public static void main(String[] args){
        SpringApplication.run(GatewayWebApplication.class,args);
    }
}

(3)application.yml跨域配置

有时候,我们需要对所有微服务跨域请求进行处理,则可以在gateway中进行跨域支持。修改application.yml,添加如下代码:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
  application:
    name: gateway-web
server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

(4)网关过滤配置

路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。 路径过滤器的范围限定为特定路径。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。如上图,根据请求路径路由到不同微服务去,这块可以使用Gateway的路由过滤功能实现。
电商(二)网关微服务搭建_第3张图片

过滤器 有 20 多个 实现 类, 包括 头部 过滤器、 路径 类 过滤器、 Hystrix 过滤器 和 变更 请求 URL 的 过滤器, 还有 参数 和 状态 码 等 其他 类型 的 过滤器。

内置的过滤器工厂有22个实现类,包括 头部过滤器、路径过滤器、Hystrix 过滤器 、请求URL 变更过滤器,还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix。

4.1 Host 路由

比如用户请求cloud.buba.com的时候,可以将请求路由给localhost:18081服务处理,如下配置:

      routes:
            - id: shop_goods_route  # 唯一标识
              uri: http://localhost:18081  #用户请求需要路由到的服务
              predicates:   # 路由规则配置(断言)
              - Host=cloud.buba.com**   # 用户请求的域名规则配置

测试请求http://cloud.buba.com:8001/brand
注意:此时要想让cloud.buba.com访问本地计算机,要配置C:\Windows\System32\drivers\etc\hosts文件,映射配置如下:

127.0.0.1 cloud.buba.com
4.1 动态路由(按照服务名转发)
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
  application:
    name: gateway-web
server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true
4.2 路径匹配过滤配置

我们还可以根据请求路径实现对应的路由过滤操作,例如请求中以/brand/路径开始的请求,都直接交给http://localhost:180801服务处理,如下配置:
所有以brand开头的请求都路由到相应的uri指定的微服务中去

      routes:
            - id: shop_goods_route
              uri: http://localhost:18081
              predicates:
              - Path=/brand**

测试请求http://localhost:8001/brand

4.3 PrefixPath 过滤配置

用户每次请求路径的时候,我们可以给真实请求加一个统一前缀,例如用户请求http://localhost:8001的时候我们让它请求真实地址http://localhost:8001/brand,如下配置:

      routes:
            - id: shop_goods_route
              uri: http://localhost:18081
              predicates:
              #- Host=cloud.buba.com**
              - Path=/**
              filters:
              - PrefixPath=/brand

测试请求http://localhost:8001/效果如下:

4.4 StripPrefix 过滤配置

很多时候也会有这么一种请求,用户请求路径是/api/brand,而真实路径是/brand,这时候我们需要去掉/api才是真实路径,此时可以使用SttripPrefix功能来实现路径的过滤操作,如下配置:

      routes:
            - id: shop_goods_route
              uri: http://localhost:18081
              predicates:
              #- Host=cloud.buba.com**
              - Path=/**
              filters:
              #- PrefixPath=/brand
              - StripPrefix=1

测试请求http://localhost:8001/api/brand

4.5 LoadBalancerClient 路由过滤器(客户端负载均衡)

上面的路由配置每次都会将请求给指定的URL处理,但如果在以后生产环境,并发量较大的时候,我们需要根据服务的名称判断来做负载均衡操作,可以使用LoadBalancerClientFilter来实现负载均衡调用。LoadBalancerClientFilter会作用在url以lb开头的路由,然后利用loadBalancer来获取服务实例,构造目标requestUrl,设置到GATEWAY_REQUEST_URL_ATTR属性中,供NettyRoutingFilter使用。

修改application.yml配置文件,代码如下:
uri: lb://goods :lb是指负载均衡,goods指的是微服务的名称

      routes:
            - id: shop_goods_route
              uri: lb://goods
              predicates:
              - Path=/**
              filters:
              - StripPrefix=1

测试请求路径http://localhost:8001/api/brand

(5)网关限流

网关可以做很多的事情,比如,限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。
电商(二)网关微服务搭建_第4张图片

5.1 令牌桶算法

令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流
电商(二)网关微服务搭建_第5张图片

电商(二)网关微服务搭建_第6张图片

5.2 使用令牌桶进行请求次数限流

spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要使用首先需要引入redis的依赖

(1)引入redis依赖

在shop-gateway的pom.xml中引入redis的依赖


<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>
        <artifactId>shop-parentartifactId>
        <groupId>com.bubagroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>shop-gatewayartifactId>
    <packaging>pompackaging>
    <modules>
        <module>shop-gateway-webmodule>
    modules>
    <properties>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
    properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
            <version>2.1.3.RELEASEversion>
        dependency>
    dependencies>
project>
(2) 定义KeyResolver

在Applicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayWebApplication定义key的获取,获取客户端IP,将IP作为key,如下代码:

package com.buba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author qlx
 */
@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication{
    public static void main(String[] args){
        SpringApplication.run(GatewayWebApplication.class,args);
    }

    /***
     * IP限流
     * @return
     */
    @Bean(name="ipKeyResolver")
    public KeyResolver userKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange){
                String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
                System.out.println("hostName:"+hostName);
                return Mono.just(hostName);
            }
        };
    }
}



(3)修改application.yml中配置项,指定限制流量的配置以及REDIS的配置
spring:
  cloud:
    gateway:
      routes:
        filters:
        - StripPrefix=1
        - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
          args:
            key-resolver: "#{@ipKeyResolver}"
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 1

解释:

  • redis-rate-limiter.replenishRate是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率
  • redis-rate-limiter.burstCapacity是指令牌桶的容量,允许在一秒钟内完成的最大请求数,将此值设置为零将阻止所有请求。
  • key-resolver: “#{@ipKeyResolver}” 用于通过SPEL表达式来指定使用哪一个KeyResolver.

如上配置:

  • 表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。
  • 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。

3.用户登录

项目中有2个重要角色,分别为管理员和用户,下面几章我们将实现购物下单和支付,用户如果没登录是没法下单和支付的,所以我们这里需要实现一个登录功能。

(1)表结构介绍 用户信息user表

CREATE TABLE `tb_user` (
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码,加密存储',
  `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
  `email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
  `created` datetime NOT NULL COMMENT '创建时间',
  `updated` datetime NOT NULL COMMENT '修改时间',
  `source_type` varchar(1) DEFAULT NULL COMMENT '会员来源:1:PC,2:H5,3:Android,4:IOS',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
  `name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `status` varchar(1) DEFAULT NULL COMMENT '使用状态(1正常 0非正常)',
  `head_pic` varchar(150) DEFAULT NULL COMMENT '头像地址',
  `qq` varchar(20) DEFAULT NULL COMMENT 'QQ号码',
  `is_mobile_check` varchar(1) DEFAULT '0' COMMENT '手机是否验证 (0否  1是)',
  `is_email_check` varchar(1) DEFAULT '0' COMMENT '邮箱是否检测(0否  1是)',
  `sex` varchar(1) DEFAULT '1' COMMENT '性别,1男,0女',
  `user_level` int(11) DEFAULT NULL COMMENT '会员等级',
  `points` int(11) DEFAULT NULL COMMENT '积分',
  `experience_value` int(11) DEFAULT NULL COMMENT '经验值',
  `birthday` datetime DEFAULT NULL COMMENT '出生年月日',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  PRIMARY KEY (`username`),
  UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

(2)用户微服务创建

2.1 在shop-service-api中创建shop-service-user-api,并将pojo拷贝到工程中
2.2 在shop-service中创建shop-service-user微服务,并引入生成的业务逻辑代码
2.3 在shop-service-user的pom.xml引入如下依赖:

<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>
        <artifactId>changgou-serviceartifactId>
        <groupId>com.changgougroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <artifactId>shop-service-userartifactId>

    
    <dependencies>
        <dependency>
            <groupId>com.changgougroupId>
            <artifactId>shop-service-user-apiartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
    dependencies>
project>
2.4 启动类创建
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.changgou.user.dao")
public class UserApplication &#123;

    public static void main(String[] args) {;
        SpringApplication.run(UserApplication.class,args);
    }
};
2.5 application.yml配置

在shop-service-user的resources中创建application.yml配置,代码如下:

server:
  port: 18089
spring:
  application:
    name: user
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/shop_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
2.6 登录

登录的时候,需要进行密码校验,这里采用了BCryptPasswordEncoder进行加密,需要将BCrypt导入到common工程中,其中BCrypt.checkpw(“明文”,”密文”)用于对比密码是否一致。

修改shop-service-user的com.buba.user.controller.UserController添加登录方法,代码如下:

/***
 * 用户登录
 */
@RequestMapping(value = "/login")
public Result login(String username,String password){;
    //查询用户信息
    User user = userService.findById(username);

    if(user!=null && BCrypt.checkpw(password,user.getPassword())){;
        return new Result(true,StatusCode.OK,"登录成功!",user);
    };
    return  new Result(false,StatusCode.LOGINERROR,"账号或者密码错误!");
}
2.7 网关关联

在我们平时工作中,并不会直接将微服务暴露出去,一般都会使用网关对接,实现对微服务的一个保护作用,如上图,当用户访问/api/user/的时候我们再根据用户请求调用用户微服务的指定方法。当然,除了/api/user/还有/api/address/、/api/areas/、/api/cities/、/api/provinces/都需要由user微服务处理,修改网关工程shop-gateway-web的application.yml配置文件,如下代码:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # 匹配所有请求
              allowedOrigins: "*" #跨域处理 允许所有的域
              allowedMethods: # 支持的方法
                - GET
                - POST
                - PUT
                - DELETE
      routes:
            - id: shop_goods_route
              uri: lb://goods
              predicates:
              - Path=/api/goods/**
              filters:
              - StripPrefix=1
              - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1
            #用户微服务
            - id: shop_user_route
              uri: lb://user
              predicates:
              - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
              filters:
              - StripPrefix=1

  application:
    name: gateway-web
  #Redis配置
  redis:
    host: 192.168.211.132
    port: 6379

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

4.鉴权处理

(1) 思路分析

1.用户通过访问微服务网关调用微服务,同时携带头文件信息
2.在微服务网关这里进行拦截,拦截后获取用户要访问的路径
3.识别用户访问的路径是否需要登录,如果需要,识别用户的身份是否能访问该路径[这里可以基于数据库设计一套权限]
4.如果需要权限访问,用户已经登录,则放行
5.如果需要权限访问,且用户未登录,则提示用户需要登录
6.用户通过网关访问用户微服务,进行登录验证
7.验证通过后,用户微服务会颁发一个令牌给网关,网关会将用户信息封装到头文件中,并响应用户
8.用户下次访问,携带头文件中的令牌信息即可识别是否登录

(2) 用户登录签发TOKEN

2.1生成令牌工具类

在shop-common中创建类entity.JwtUtil,主要辅助生成Jwt令牌信息,代码如下:

package com.buba.entity;

import org.bouncycastle.crypto.tls.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
 * @author qlx
 */
public class JwtUtil {;

    //有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时

    //Jwt令牌信息
    public static final String JWT_KEY = "itcast";

    public static String createJWT(String id, String subject, Long ttlMillis) {;
        //指定算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //当前系统时间
        long nowMillis = System.currentTimeMillis();
        //令牌签发时间
        Date now = new Date(nowMillis);

        //如果令牌有效期为null,则默认设置有效期1小时
        if(ttlMillis==null){;
            ttlMillis=JwtUtil.JWT_TTL;
        };

        //令牌过期时间设置
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);

        //生成秘钥
        SecretKey secretKey = generalKey();

        //封装Jwt令牌信息
        JwtBuilder builder = Jwts.builder()
                .setId(id)                    //唯一的ID
                .setSubject(subject)          // 主题  可以是JSON数据
                .setIssuer("admin")          // 签发者
                .setIssuedAt(now)             // 签发时间
                .signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙
                .setExpiration(expDate);      // 设置过期时间
        return builder.compact();
    };

    /**
     * 生成加密 secretKey
     * @return
     */
    public static SecretKey generalKey() {;
        byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    };


    /**
     * 解析令牌数据
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {;
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    };
};

(3) 用户登录成功 则 签发TOKEN,修改登录的方法:

修改shop-service-user的com.buba.user.controller.UserController添加登录方法,代码如下:

/***
 * 用户登录
 */
@RequestMapping(value = "/login")
public Result login(String username,String password){
    //查询用户信息
    User user = userService.findById(username);

    if(user!=null && BCrypt.checkpw(password,user.getPassword())){
        //设置令牌信息
        Map<String,Object> info = new HashMap<String,Object>();
        info.put("role","USER");
        info.put("success","SUCCESS");
        info.put("username",username);
        //生成令牌
        String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(info),null);
        return new Result(true,StatusCode.OK,"登录成功!",jwt);
   }
    return  new Result(false,StatusCode.LOGINERROR,"账号或者密码错误!");
}

(4) 网关过滤器拦截请求处理

拷贝JwtUtil到shop-gateway-web中

(5) 自定义全局过滤器

创建 过滤器类AuthorizeFilter代码如下:

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered{

    //令牌头名字
    private static final String AUTHORIZE_TOKEN = "Authorization";

    /***
     * 全局过滤器
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取Request、Response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //获取请求的URI
        String path = request.getURI().getPath();

        //如果是登录、goods等开放的微服务[这里的goods部分开放],则直接放行,这里不做完整演示,完整演示需要设计一套权限系统
        if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {
            //放行
            Mono<Void> filter = chain.filter(exchange);
            return filter;
        }

        //获取头文件中的令牌信息
        String tokent = request.getHeaders().getFirst(AUTHORIZE_TOKEN);

        //如果头文件中没有,则从请求参数中获取
        if (StringUtils.isEmpty(tokent)) {
            tokent = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
        }

        //如果为空,则输出错误代码
        if (StringUtils.isEmpty(tokent)) {
            //设置方法不允许被访问,405错误代码
           response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
           return response.setComplete();
        }

        //解析令牌数据
        try{
            Claims claims = JwtUtil.parseJWT(tokent);
       }catch (Exception e){
            e.printStackTrace();
            //解析失败,响应401错误
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //放行
        return chain.filter(exchange);
    }


    /***
     * 过滤器执行顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

(6) 配置过滤规则

修改网关系统的yml文件:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # 匹配所有请求
              allowedOrigins: "*" #跨域处理 允许所有的域
              allowedMethods: # 支持的方法
                - GET
                - POST
                - PUT
                - DELETE
      routes:
            - id: shop_goods_route
              uri: lb://goods
              predicates:
              - Path=/api/album/**,/api/brand/**,/api/cache/**,/api/categoryBrand/**,/api/category/**,/api/para/**,/api/pref/**,/api/sku/**,/api/spec/**,/api/spu/**,/api/stockBack/**,/api/template/**
              filters:
              - StripPrefix=1
              - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1
            #用户微服务
            - id: changgou_user_route
              uri: lb://user
              predicates:
              - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
              filters:
              - StripPrefix=1

  application:
    name: gateway-web
  #Redis配置
  redis:
    host: 192.168.211.132
    port: 6379

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

5 会话保持

会话保持是指在负载均衡器上的一种机制,可以识别客户端与服务器之间交互过程的关连性,在作负载均衡的同时还保证一系列相关连的访问请求都会分配到一台机器上。用人话来表述就是:在一次会话过程中发起的多个请求都会落到同一台机器上。
用户每次请求的时候,我们都需要获取令牌数据,方法有多重,可以在每次提交的时候,将数据提交到头文件中,也可以将数据存储到Cookie中,每次从Cookie中校验数据,还可以每次将令牌数据以参数的方式提交到网关,这里面采用Cookie的方式比较容易实现。

(1) 登录封装Cookie

修改user微服务,每次登录的时候,添加令牌信息到Cookie中,修改shop-service-user的com.buba.user.controller.UserController的login方法,代码如下:

(2)过滤器获取令牌数据

每次在网关中通过过滤器获取Cookie中的令牌,然后对令牌数据进行解析,修改微服务网关shop-gateway-web中的AuthorizeFilter,代码如下:

登录后测试,可以识别用户身份,不登录无法识别。如下访问http://localhost:8001/api/user会携带令牌数据:

(3) 添加Header信息

我们还可以在Gateway的全局过滤器中添加请求头信息,例如可以讲令牌信息添加到请求头中,在微服务中获取头信息,如下代码:

修改微服务网关中的AuthorizeFilter过滤器,在令牌信息校验那块将令牌加入到请求头中,如下代码:

在shop-service-user微服务的UserController的findAll方法中获取请求头测试,代码如下:

后台输出令牌数据如下:

你可能感兴趣的:(微服务,java,spring,cloud)