赞
踩
Spring Cloud Gateway 用"Netty + Webflux"实现,不需要导入Web依赖。
在cloud父项目中新建一个模块Module,创建子模块网关cloud-gateway-gateway9527
在POM文件中添加如下依赖
<?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> <artifactId>cloud</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-gateway-gateway9527</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <!-- 引入网关Gateway依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <!-- 引入Eureka client依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- actuator监控信息完善 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> </project>
在gateway子模块中创建包com.zzx,在包下创建主启动类GatewayMain9527
package com.zzx;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@Slf4j
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
log.info("************ GatewayMain9527服务 启动成功 *************");
}
}
在resources目录下创建application.yml文件,配置如下
server:
port: 9527
spring:
cloud:
gateway:
routes:
# 路由ID,没有固定规则但要求唯一,建议配合服务名
- id: cloud-payment-provider
# 匹配后提供服务的路由地址 (即目标服务地址)
uri: http://localhost:8001
# 断言会接收一个输入参数,返回一个布尔值结果
predicates:
# 路径相匹配的进行路由
- Path=/payment/*
测试
1)先开启7001和7002的Eureka服务,payment8001服务提供者和gateway9527服务。
2)在浏览器使用9527端口,也就是网关进行访问payment8001服务即可。
在浏览器输入:http://localhost:9527/payment/index
在子模块cloud-gateway-gateway9527中的com.zzx包下,创建包config,并在包下创建GatewayConfig
package com.zzx.config; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayConfig { @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder){ //获取路由 RouteLocatorBuilder.Builder routes = builder.routes(); /** * 设置路由 * 1.路由id * 2.路由匹配规则 * 3.目标地址 */ routes.route("path_route",r->r.path("/payment/*").uri("http://localhost:8001/")).build(); return routes.build(); } }
测试
1)将yml文件中的gateway配置注释掉,然后重启该服务。
2)在浏览器上访问:http://localhost:9527/payment/index
再添加一个服务提供者,用以实现Gateway网关的动态路由的功能。
1)复制payment8001服务,然后点击cloud父工程,ctrl+v进行粘贴,修改名字为8002
2)修改POM文件:
<artifactId>cloud-provider-payment8002</artifactId>
3)将POM右键,选择添加为Maven项目Add as Maven Project
4)修改com.zzx包下的启动类的名字以及类中的名字
package com.zzx; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 主启动类 */ @SpringBootApplication @Slf4j public class PaymentMain8002 { public static void main(String[] args) { SpringApplication.run(PaymentMain8002.class,args); log.info("****** PaymentMain8002服务启动成功 *****"); } }
5)将yml文件的端口号port和instance-id的名字有8001部分都修改为8002
然后在启动类中运行该payment8002服务。
修改gateway9527项目的yml文件
server: port: 9527 eureka: instance: # 注册名 instance-id: cloud-gateway-gateway9527 client: service-url: # Eureka server的地址 #集群 defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka #单机 #defaultZone: http://localhost:7001/eureka/ spring: application: #设置应用名 name: cloud-gateway cloud: gateway: routes: # 路由ID,没有固定规则但要求唯一,建议配合服务名 - id: cloud-payment-provider # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字 uri: lb://CLOUD-PAYMENT-PROVIDER # 断言会接收一个输入参数,返回一个布尔值结果 predicates: # 路径相匹配的进行路由 - Path=/payment/*
注释之前的配置文件GatewayConfig中的方法。
在服务提供者payment8001和payment8002中的com.zzx.controller的PaymentController类中添加如下代码
@Value("${server.port}")
private String port;
@GetMapping("lb")
public String lb(){
return port;
}
即通过该lb的url请求来测试动态路由是否配置生效。
测试动态路由是否配置生效。
1)重启payment8001和payment8002以及gateway9527服务
2)浏览器中访问:http://localhost:9527/payment/lb
此时刷新后随即出现8001或8002,估计是轮询的策略。
UTC时间格式的时间参数时间生成方法
package demo;
import java.time.ZonedDateTime;
public class Test1 {
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
}
}
Postman的下载地址:https://dl.pstmn.io/download/latest/win64
Postman即用来URL请求测试的软件,可以很方便的添加任何请求参数。
点击+号即可创建新的请求窗口,用来发送URL请求
After路由断言
predicates:
- Path=/payment/*
# 在这个时间点之后才能访问
- After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
即使用生成的UTC时间格式的时间,在该时间之后才允许访问。
Before路由断言
predicates:
- Path=/payment/*
# 在这个时间点之前才能访问
- Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
即使用生成的UTC时间格式的时间,在该时间之前才允许访问。
Between路由断言
predicates:
- Path=/payment/*
# 在两个时间内才能访问
- Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
即使用生成的UTC时间格式的时间,在两个时间内才允许访问。
Cookie路由断言
1)Cookie验证的是Cookie中保存的信息,Cookie断言和上面介绍的两种断言使用方式大同小异,唯一的不同是它必须连同属性值一同验证,不能单独只验证属性是否存在。
predicates:
- Path=/payment/*
- Cookie=username,zzx
即Cookie的username的值为zzx才允许访问
2)使用postman进行测试,在headers添加Cookie即可
此时如果不带Cookie,则报404错误
Header路由断言
1)这个断言会检查Header中是否包含了响应的属性,通常可以用来验证请求是否携带了访问令牌。
predicates:
- Path=/payment/*
- Header=X-Request-Id,\d+
2)使用postman进行测试,在headers添加X-Request-Id即可
Host路由断言
1)Host 路由断言 Factory包括一个参数:host name列表。使用Ant路径匹配规则, .作为分隔符。访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个参数使用,号隔开。
predicates:
- Path=/payment/*
- Host=127.0.0.1,localhost
2)使用postman进行测试,在headers添加Host即可
Method路由断言
1)即Request请求的方式,例如GET或POST请求,不匹配则无法进行请求
predicates:
- Path=/payment/*
- Method=GET,POST
2)可以使用postman,也可以使用浏览器直接访问,因为不需要加任何参数
Query路由断言
1)请求断言也是在业务中经常使用的,它会从ServerHttpRequest中的Parameters列表中查询指定的属性,例如验证参数的类型等
predicates:
- Path=/payment/*
- Query=age,\d+
2)在参数Params中添加age属性,值为正整数即可访问
https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories
server: port: 9527 eureka: instance: # 注册名 instance-id: cloud-gateway-gateway9527 client: service-url: # Eureka server的地址 #集群 defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka #单机 #defaultZone: http://localhost:7001/eureka/ spring: application: #设置应用名 name: cloud-gateway cloud: gateway: routes: # 路由ID,没有固定规则但要求唯一,建议配合服务名 - id: cloud-payment-provider # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字 uri: lb://CLOUD-PAYMENT-PROVIDER # 断言会接收一个输入参数,返回一个布尔值结果 predicates: # 路径相匹配的进行路由 - Path=/payment/* # 在这个时间点之后才能访问 # - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # 在这个时间点之前才能访问 # - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # 在两个时间内才能访问 # - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # - Cookie=username,zzx # - Header=X-Request-Id,\d+ # - Host=127.0.0.1,localhost # - Method=GET,POST # - Query=age,\d+ #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改 filters: # 修改原始响应的状态码 - SetStatus=250
http://localhost:9527/payment/lb
在gateway9527服务的com.zzx.config包下,创建日志网关过滤器类LogGatewayFilterFactory
package com.zzx.config; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * 日志网关过滤器 */ @Component @Slf4j public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> { public LogGatewayFilterFactory() { super(Config.class); } /** * 表示配置填写顺序 * @return */ @Override public List<String> shortcutFieldOrder() { return Arrays.asList("consoleLog"); } /** * 执行过滤的逻辑 * @param config * @return */ @Override public GatewayFilter apply(Config config) { return ((exchange, chain) -> { if(config.consoleLog){ log.info("********* consoleLog日志 开启 ********"); } return chain.filter(exchange); }); } /** * 过滤器使用的配置内容 * */ @Data public static class Config{ private boolean consoleLog; } }
在YML文件中,添加如下
filters:
# 控制日志是否开启
- Log=true
即开启日志,该true会被consoleLog获取到。 然后即可打印对应的日志。
测试
1)重启Gateway9527服务
2)在浏览器中访问:http://localhost:9527/payment/lb
步骤:
1、类名必须叫做XxxGatewayFilterFactory,注入到Spring容器后使用时的名称就叫做Xxx。
2、创建一个静态内部类Config, 里面的属性为配置文件中配置的参数, - 过滤器名称=参数1,参数2…
2、类必须继承 AbstractGatewayFilterFactory,让父类帮实现配置参数的处理。
3、重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合
return Arrays.asList(“参数1”,参数2…)
4、无参构造方法中super(Config.class)
5、编写过滤逻辑 public GatewayFilter apply(Config config)
在gateway9527服务的com.zzx.config包下,创建用户鉴权全局过滤器类AuthGlobalFilter
package com.zzx.config; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 用户鉴权全局过滤器 */ @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { /** * 自定义全局过滤器逻辑 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1。请求中获取Token令牌 String token = exchange.getRequest().getQueryParams().getFirst("token"); //2.判断token是否为空 if(StringUtils.isEmpty(token)){ System.out.println("鉴权失败,令牌为空"); //将状态码设置为未授权 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } //3。判断token是否有效 if(!token.equals("zzx")){ System.out.println("token令牌无效"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } /** * 全局过滤器执行顺序 数值越小,优先级越高 * @return */ @Override public int getOrder() { return 0; } }
使用postman测试,在params中添加一个token进行测试
https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#global-filters
跨域
即当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
在resources目录下创建index.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <script> $.get("http://localhost:9527/payment/lb?token=zzx",function(data,status){ alert("Data: " + data + "\nStatus: " + status); }); </script> </html>
配置允许跨域
1)在未配置允许跨域之前,打开该index.html文件时,如图
2)在yml文件中配置允许跨域
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowCredentials: true
allowedOriginPatterns: "*"
allowedMethods: "*"
allowedHeaders: "*"
add-to-simple-url-handler-mapping: true
3)配置后,打开该index.html文件时,如图
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录(SSO)场景。
JWT优点
1)无状态
2)适合移动端应用
3)单点登录友好
用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT 的三个部分依次如下:
1)头部(header)
JSON对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),统一写为 JWT。
{
"alg": "HS256",
"typ": "JWT"
}
2)载荷(payload)
内容又可以分为3种标准
1.标准中注册的声明
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
2.公共的声明
公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息(非敏感信息)。
3.私有的声明
私有声明是提供者和消费者所共同定义的声明。需要注意的是,不要存放敏感信息
base64编码,任何人获取到jwt之后都可以解码!!
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
3)签证(signature)
这部分就是 JWT 防篡改的精髓,其值是对前两部分base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret,
)
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
在cloud父工程下,创建子模块项目cloud-auth-user6500
在cloud-auth-user6500项目的pom文件中引入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- eureka client 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 引入JWT依赖 --> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.23</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.2.1</version> </dependency> </dependencies>
在com.zzx中创建一个包utils,创建工具类JWTUtils
package com.zzx.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Date; import java.util.concurrent.TimeUnit; public class JWTUtils { // 签发人 private static final String ISSUSER = "zzx"; // 过期时间 1分钟 private static final long TOKEN_EXPIRE_TIME = 60*1000; // 秘钥 public static final String SECRET_KEY = "zzx-13256"; /** * 生成令牌 * @return */ public static String token(){ Date now = new Date(); Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY); // 1.创建JWT String token = JWT.create(). // 签发人 withIssuer(ISSUSER) // 签发时间 .withIssuedAt(now) // 过期时间 .withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME)) // 加密算法 .sign(hmac256); return token; } /** * 验证令牌 * @return */ public static boolean verify(String token){ try { Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY); JWTVerifier verifier = JWT.require(hmac256) // 签发人 .withIssuer(ISSUSER) .build(); // 如果校验有问题则抛出异常 DecodedJWT verify = verifier.verify(token); return true; } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTVerificationException e) { e.printStackTrace(); } return false; } public static void main(String[] args) throws InterruptedException { String token = token(); System.out.println(token); boolean verify = verify(token); System.out.println(verify); verify = verify(token+" 11"); System.out.println(verify); TimeUnit.SECONDS.sleep(61); verify = verify(token); System.out.println(verify); } }
在该工具类JWTUtils中创建main方法用来测试该工具类。后面需要删掉。
在com.zzx中创建一个包common,创建类Result
package com.zzx.common; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 返回实体类 */ @AllArgsConstructor @NoArgsConstructor @Data @Builder public class Result { // 状态码 private int code; // 描述信息 private String msg; // token令牌 private String token; }
即用该类来封装返回值信息。
在com.zzx中创建一个包controller,创建控制层类UserController
package com.zzx.controller; import com.zzx.common.Result; import com.zzx.utils.JWTUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 用户控制层 */ @RestController @RequestMapping("user") public class UserController { /** * 登录 * @param username * @param password */ @PostMapping("login") public Result login(String username, String password){ // 1.验证用户名和密码 // TODO 模拟数据库操作 if("zzx".equals(username)&&"123456".equals(password)){ // 2.生成令牌 String token = JWTUtils.token(); return Result.builder().code(200).msg("success").token(token).build(); }else{ return Result.builder().code(500).msg("用户名或密码不正确").build(); } } }
在resources目录下创建一个application.yml配置文件
server: port: 6500 eureka: instance: # 注册名 instance-id: cloud-auth-user6500 client: service-url: # Eureka server的地址 #集群 defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka #单机 #defaultZone: http://localhost:7001/eureka/ spring: application: #设置应用名 name: cloud-auth-user
在com.zzx中,修改主启动类Main,修改为UserMain6500
package com.zzx; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 主启动类 */ @Slf4j @SpringBootApplication public class UserMain6500 { public static void main(String[] args) { SpringApplication.run(UserMain6500.class,args); log.info("************ UserMain6500服务 启动成功 ************"); } }
测试User控制层的login方法
1)启动eureka服务eureka7001和eureka7002以及user6500
2)在postman中,使用POST请求传入用户名和密码,对该url进行测试
即在网关过滤器中加入JWT来鉴权
在gateway9527项目的POM文件中添加JWT依赖
<!-- 引入JWT依赖 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.1</version>
</dependency>
将user6500项目中com.zzx.utils包下的JWTUtils复制到gateway9527项目的com.zzx.utils包下
package com.zzx.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Date; public class JWTUtils { // 签发人 private static final String ISSUSER = "zzx"; // 过期时间 1分钟 private static final long TOKEN_EXPIRE_TIME = 60*1000; // 秘钥 public static final String SECRET_KEY = "zzx-13256"; /** * 生成令牌 * @return */ public static String token(){ Date now = new Date(); Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY); // 1.创建JWT String token = JWT.create(). // 签发人 withIssuer(ISSUSER) // 签发时间 .withIssuedAt(now) // 过期时间 .withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME)) // 加密算法 .sign(hmac256); return token; } /** * 验证令牌 * @return */ public static boolean verify(String token){ try { Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY); JWTVerifier verifier = JWT.require(hmac256) // 签发人 .withIssuer(ISSUSER) .build(); // 如果校验有问题则抛出异常 DecodedJWT verify = verifier.verify(token); return true; } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTVerificationException e) { e.printStackTrace(); } return false; } }
修改application.yml文件
server: port: 9527 eureka: instance: # 注册名 instance-id: cloud-gateway-gateway9527 client: service-url: # Eureka server的地址 #集群 defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka #单机 #defaultZone: http://localhost:7001/eureka/ org: my: jwt: # 跳过认证路由 skipAuthUrls: - /user/login spring: application: #设置应用名 name: cloud-gateway cloud: gateway: # 路由配置 routes: # 路由ID,没有固定规则但要求唯一,建议配合服务名 - id: cloud-auth-user # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字 uri: lb://CLOUD-AUTH-USER # 断言会接收一个输入参数,返回一个布尔值结果 predicates: # 路径相匹配的进行路由 - Path=/user/* # 路由ID,没有固定规则但要求唯一,建议配合服务名 - id: cloud-payment-provider # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字 uri: lb://CLOUD-PAYMENT-PROVIDER # 断言会接收一个输入参数,返回一个布尔值结果 predicates: # 路径相匹配的进行路由 - Path=/payment/* # 在这个时间点之后才能访问 # - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # 在这个时间点之前才能访问 # - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # 在两个时间内才能访问 # - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai] # - Cookie=username,zzx # - Header=X-Request-Id,\d+ # - Host=127.0.0.1,localhost # - Method=GET,POST # - Query=age,\d+ #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改 filters: # 修改原始响应的状态码 # - SetStatus=250 # 控制日志是否开启 - Log=true globalcors: cors-configurations: '[/**]': allowCredentials: true allowedOriginPatterns: "*" allowedMethods: "*" allowedHeaders: "*" add-to-simple-url-handler-mapping: true
即需要添加一个user微服务的路由,以及跳过权限验证的Path路径
将gateway9527项目的com.zzx.config包下原先的用户鉴权类AuthGlobalFilter上面的@Component注解注释掉,即不使用这个类来鉴权;创建使用另一个类UserAuthGlobalFilter来鉴权
package com.zzx.config; import com.alibaba.fastjson.JSONObject; import com.zzx.common.Response; import com.zzx.utils.JWTUtils; import io.micrometer.common.util.StringUtils; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; 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.http.HttpStatus; 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; import java.nio.charset.StandardCharsets; /** * 用户鉴权全局过滤器 */ @Data @ConfigurationProperties("org.my.jwt") @Component @Slf4j public class UserAuthGlobalFilter implements GlobalFilter, Ordered { private String[] skipAuthUrls; /** * 过滤器逻辑 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求url地址 String path = exchange.getRequest().getURI().getPath(); // 跳过不需要验证的路径 if(skipAuthUrls!=null && isSKip(path)){ return chain.filter(exchange); } // 1.从请求头中获取token String token = exchange.getRequest().getHeaders().getFirst("token"); // 2.判断token if(StringUtils.isEmpty(token)){ // 3.设置响应 ServerHttpResponse response = exchange.getResponse(); // 4.设置响应状态码 response.setStatusCode(HttpStatus.OK); // 5.设置响应头 response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); // 6.创建响应对象 Response res = new Response(200, "token 参数缺失"); // 7.对象转字符串 byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8); // 8.数据流返回数据 DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Flux.just(wrap)); } // 验证token boolean verify = JWTUtils.verify(token); if(!verify){ // 3.设置响应 ServerHttpResponse response = exchange.getResponse(); // 4.设置响应状态码 response.setStatusCode(HttpStatus.OK); // 5.设置响应头 response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); // 6.创建响应对象 Response res = new Response(200, "token 失效"); // 7.对象转字符串 byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8); // 8.数据流返回数据 DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Flux.just(wrap)); } // token 令牌通过 return chain.filter(exchange); } @Override public int getOrder() { return 0; } private boolean isSKip(String url){ for (String skipAuthUrl :skipAuthUrls) { if(url.startsWith(skipAuthUrl)){ return true; } } return false; } }
测试
1)先启动eureka7001和eureka7002,还有Payment8001和Payment8002,以及user6500和gateway9527服务。
2)使用postman工具来测试,先进行登录,拿到用户的token
3)再切换到之前9527的url测试
token有效时
token过期失效时
没有token时(即未登录时)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。