做网站切图,营销推广的方法有哪些,青岛建网站选青岛博采网络,上海外贸人才网文章目录 一、SpringCloudGateway服务网关概论1、SpringCloudGateway服务网关概论2、SpringCloudGateway的三大核心概念 二、SpringCloudGateway的路由及断言1、子模块项目SpringCloudGateway的搭建2、SpringCloudGateway_Java API构建路由3、SpringCloudGateway的动态路由功能… 文章目录 一、SpringCloudGateway服务网关概论1、SpringCloudGateway服务网关概论2、SpringCloudGateway的三大核心概念 二、SpringCloudGateway的路由及断言1、子模块项目SpringCloudGateway的搭建2、SpringCloudGateway_Java API构建路由3、SpringCloudGateway的动态路由功能4、SpringCloudGateway的路由断言 三、SpringCloudGateway的过滤器及跨域1、SpringCloudGateway的过滤器2、网关过滤器GatewayFilter3、自定义网关过滤器GatewayFilter4、自定义全局过滤器GlobalFilter5、内置全局过滤器6、服务网关Gateway实现跨域 四、SpringCloudGateway实现用户鉴权1、JsonWebToken概论2、创建用户的微服务及登录操作3、服务网关Gateway实现用户鉴权 总结 一、SpringCloudGateway服务网关概论
1、SpringCloudGateway服务网关概论
Spring Cloud Gateway 用Netty Webflux实现不需要导入Web依赖。
Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作这些线程称为Loop线程而业务交给响应式编程框架处理响应式编程是非常灵活的用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行而不阻塞的操作依然可以在Loop线程中进行处理大大提高了Loop线程的利用率。 即Webflux中的Loop线程不仅可以处理请求和响应请求还可以对业务中不阻塞的操作进行处理从而提高它的利用率。阻塞的操作由work线程进行处理。Webflux虽然可以兼容多个底层的通信框架但是一般情况下底层使用的还是Netty毕竟Netty是目前业界认可的最高性能的通信框架。 Netty 是一个基于NIO的客户、服务器端的编程框架。提供异步的、事件驱动的网络应用程序框架和工具用以快速开发高性能、高可靠性的网络服务器和客户端程序。Spring Cloud Gateway特点 1易于编写谓词( Predicates )和过滤器 Filters ) 。其Predicates和Filters 可作用于特定路由。 2支持路径重写。 3支持动态路由。 4集成了Spring Cloud DiscoveryClient。
2、SpringCloudGateway的三大核心概念
路由(Route) 这是网关的基本构建块。它由一个ID一个目标URI一组断言和一组过滤器定义。如果断言为真则路由匹配。 即根据URL请求去匹配路由。断言(predicate) 输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容例如headers或参数。匹配请求内容。 匹配完路由后每个路由上面都会有断言然后根据断言来判断是否可以进行路由。过滤(filter) 在匹配完路由和断言为真后可以在请求被路由前或者之后对请求进行修改。 即根据业务对其进行监控限流日志输出等等。
二、SpringCloudGateway的路由及断言
1、子模块项目SpringCloudGateway的搭建 在cloud父项目中新建一个模块Module创建子模块网关cloud-gateway-gateway9527 在POM文件中添加如下依赖 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentartifactIdcloud/artifactIdgroupIdcom.zzx/groupIdversion1.0-SNAPSHOT/version/parentmodelVersion4.0.0/modelVersionartifactIdcloud-gateway-gateway9527/artifactIdpropertiesmaven.compiler.source17/maven.compiler.sourcemaven.compiler.target17/maven.compiler.target/propertiesdependencies!-- 引入网关Gateway依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.22/version/dependency!-- 引入Eureka client依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!-- actuator监控信息完善 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-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
2、SpringCloudGateway_Java API构建路由 在子模块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 {Beanpublic 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
3、SpringCloudGateway的动态路由功能 再添加一个服务提供者用以实现Gateway网关的动态路由的功能。 1复制payment8001服务然后点击cloud父工程ctrlv进行粘贴修改名字为8002 2修改POM文件 artifactIdcloud-provider-payment8002/artifactId3将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: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/
spring:application:#设置应用名name: cloud-gatewaycloud: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估计是轮询的策略。
4、SpringCloudGateway的路由断言 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/*# 在这个时间点之后才能访问- After2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]即使用生成的UTC时间格式的时间在该时间之后才允许访问。 Before路由断言 predicates:- Path/payment/*# 在这个时间点之前才能访问- Before2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]即使用生成的UTC时间格式的时间在该时间之前才允许访问。 Between路由断言 predicates:- Path/payment/*# 在两个时间内才能访问- Between2030-04-28T11:50:49.21357240008:00[Asia/Shanghai],2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]即使用生成的UTC时间格式的时间在两个时间内才允许访问。 Cookie路由断言 1Cookie验证的是Cookie中保存的信息Cookie断言和上面介绍的两种断言使用方式大同小异唯一的不同是它必须连同属性值一同验证不能单独只验证属性是否存在。 predicates:- Path/payment/*- Cookieusername,zzx即Cookie的username的值为zzx才允许访问 2使用postman进行测试在headers添加Cookie即可 此时如果不带Cookie则报404错误 Header路由断言 1这个断言会检查Header中是否包含了响应的属性通常可以用来验证请求是否携带了访问令牌。 predicates:- Path/payment/*- HeaderX-Request-Id,\d2使用postman进行测试在headers添加X-Request-Id即可 Host路由断言 1Host 路由断言 Factory包括一个参数host name列表。使用Ant路径匹配规则 .作为分隔符。访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个参数使用,号隔开。 predicates:- Path/payment/*- Host127.0.0.1,localhost2使用postman进行测试在headers添加Host即可 Method路由断言 1即Request请求的方式例如GET或POST请求不匹配则无法进行请求 predicates:- Path/payment/*- MethodGET,POST2可以使用postman也可以使用浏览器直接访问因为不需要加任何参数 Query路由断言 1请求断言也是在业务中经常使用的它会从ServerHttpRequest中的Parameters列表中查询指定的属性例如验证参数的类型等 predicates:- Path/payment/*- Queryage,\d2在参数Params中添加age属性值为正整数即可访问
三、SpringCloudGateway的过滤器及跨域
1、SpringCloudGateway的过滤器
过滤器Filter 在用户访问各个服务前应在网关层统一做好鉴权、限流等工作。 1Filter的生命周期 根据生命周期可以将Spring Cloud Gateway中的Filter分为PRE和POST两种。 PRE代表在请求被路由之前执行该过滤器此种过滤器可用来实现参数校验、权限校验、流量监控、日志输出、协议转换等功能。 POST代表在请求被路由到微服务之后执行该过滤器。此种过滤器可用来实现响应头的修改如添加标准的HTTP Header )、收集统计信息和指标、将响应发送给客户端、输出日志、流量监控等功能。 即PRE是路由之前POST是路由之后。 2Filter分类 根据作用范围Filter可以分为以下两种。 GatewayFilter网关过滤器此种过滤器只应用在单个路由或者一个分组的路由上。 GlobalFilter全局过滤器此种过滤器会应用在所有的路由上。
2、网关过滤器GatewayFilter
官方的配置文档https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories使用内置过滤器SetStatus 1在yml文件中的filters下添加过滤器server:port: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/
spring:application:#设置应用名name: cloud-gatewaycloud:gateway:routes:# 路由ID没有固定规则但要求唯一建议配合服务名- id: cloud-payment-provider# 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字uri: lb://CLOUD-PAYMENT-PROVIDER# 断言会接收一个输入参数返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path/payment/*# 在这个时间点之后才能访问
# - After2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]# 在这个时间点之前才能访问
# - Before2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]# 在两个时间内才能访问
# - Between2030-04-28T11:50:49.21357240008:00[Asia/Shanghai],2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]
# - Cookieusername,zzx
# - HeaderX-Request-Id,\d
# - Host127.0.0.1,localhost
# - MethodGET,POST
# - Queryage,\d#过滤器请求在传递过程中可以通过过滤器对其进行一定的修改filters:# 修改原始响应的状态码- SetStatus2502在浏览器测试http://localhost:9527/payment/lb 此时响应码成功修改为250。
3、自定义网关过滤器GatewayFilter 在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 AbstractGatewayFilterFactoryLogGatewayFilterFactory.Config {public LogGatewayFilterFactory() {super(Config.class);}/*** 表示配置填写顺序* return*/Overridepublic ListString shortcutFieldOrder() {return Arrays.asList(consoleLog);}/*** 执行过滤的逻辑* param config* return*/Overridepublic GatewayFilter apply(Config config) {return ((exchange, chain) - {if(config.consoleLog){log.info(********* consoleLog日志 开启 ********);}return chain.filter(exchange);});}/*** 过滤器使用的配置内容**/Datapublic static class Config{private boolean consoleLog;}} 在YML文件中添加如下 filters:# 控制日志是否开启- Logtrue即开启日志该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)
4、自定义全局过滤器GlobalFilter 在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*/Overridepublic MonoVoid 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*/Overridepublic int getOrder() {return 0;}
} 使用postman测试在params中添加一个token进行测试
5、内置全局过滤器
官方的配置文档https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#global-filters SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理的。路由过滤器Forward路由过滤器LoadBalancerClientNetty路由过滤器Netty写响应过滤器Netty Write Response FRouteToRequestUrl 过滤器路由过滤器 (Websocket Routing Filter)网关指标过滤器Gateway Metrics Filter)组合式全局过滤器和网关过滤器排序Combined Global Filter and GatewayFilter Ordering路由Marking An Exchange As Routed
6、服务网关Gateway实现跨域 跨域 即当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域 在resources目录下创建index.html文件 !DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/title
/head
body/body
script srchttp://libs.baidu.com/jquery/2.0.0/jquery.min.js/script
script$.get(http://localhost:9527/payment/lb?tokenzzx,function(data,status){alert(Data: data \nStatus: status);});
/script
/html 配置允许跨域 1在未配置允许跨域之前打开该index.html文件时如图 2在yml文件中配置允许跨域 spring:cloud:gateway:globalcors:cors-configurations:[/**]:allowCredentials: trueallowedOriginPatterns: *allowedMethods: *allowedHeaders: *add-to-simple-url-handler-mapping: true
3配置后打开该index.html文件时如图
四、SpringCloudGateway实现用户鉴权
1、JsonWebToken概论 JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录SSO场景。 JWT优点 1无状态 2适合移动端应用 3单点登录友好 用户与服务端通信的时候都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据服务器在生成这个对象的时候会加上签名服务器就不保存任何 session 数据了也就是说服务器变成无状态了从而比较容易实现扩展。 JWT 的三个部分依次如下: 1头部header) JSON对象描述 JWT 的元数据。其中 alg 属性表示签名的算法algorithm默认是 HMAC SHA256写成 HS256typ 属性表示这个令牌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字段里面。
2、创建用户的微服务及登录操作 在cloud父工程下创建子模块项目cloud-auth-user6500 在cloud-auth-user6500项目的pom文件中引入依赖 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- redis --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- eureka client 依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.22/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!-- 引入JWT依赖 --!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.23/version/dependency!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --dependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion4.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.创建JWTString 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: 6500eureka:instance:# 注册名instance-id: cloud-auth-user6500client: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进行测试
3、服务网关Gateway实现用户鉴权
即在网关过滤器中加入JWT来鉴权 在gateway9527项目的POM文件中添加JWT依赖 !-- 引入JWT依赖 --!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.23/version/dependency!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --dependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion4.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.创建JWTString 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: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/org:my:jwt:# 跳过认证路由skipAuthUrls:- /user/loginspring:application:#设置应用名name: cloud-gatewaycloud: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/*# 在这个时间点之后才能访问
# - After2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]# 在这个时间点之前才能访问
# - Before2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]# 在两个时间内才能访问
# - Between2030-04-28T11:50:49.21357240008:00[Asia/Shanghai],2030-04-28T11:50:49.21357240008:00[Asia/Shanghai]
# - Cookieusername,zzx
# - HeaderX-Request-Id,\d
# - Host127.0.0.1,localhost
# - MethodGET,POST
# - Queryage,\d#过滤器请求在传递过程中可以通过过滤器对其进行一定的修改filters:# 修改原始响应的状态码
# - SetStatus250# 控制日志是否开启- Logtrueglobalcors:cors-configurations:[/**]:allowCredentials: trueallowedOriginPatterns: *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*/Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求url地址String path exchange.getRequest().getURI().getPath();// 跳过不需要验证的路径if(skipAuthUrls!null isSKip(path)){return chain.filter(exchange);}// 1.从请求头中获取tokenString token exchange.getRequest().getHeaders().getFirst(token);// 2.判断tokenif(StringUtils.isEmpty(token)){// 3.设置响应ServerHttpResponse response exchange.getResponse();// 4.设置响应状态码response.setStatusCode(HttpStatus.OK);// 5.设置响应头response.getHeaders().add(Content-Type,application/json;charsetUTF-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));}// 验证tokenboolean verify JWTUtils.verify(token);if(!verify){// 3.设置响应ServerHttpResponse response exchange.getResponse();// 4.设置响应状态码response.setStatusCode(HttpStatus.OK);// 5.设置响应头response.getHeaders().add(Content-Type,application/json;charsetUTF-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);}Overridepublic 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时即未登录时
总结
Spring Cloud Gateway 用Netty Webflux实现不需要导入Web依赖。 1Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作这些线程称为Loop线程。Webflux中的Loop线程不仅可以处理请求和响应请求还可以对业务中不阻塞的操作进行处理从而提高它的利用率。阻塞的操作由work线程进行处理。 Webflux底层使用的还是NettyNetty是目前业界认可的最高性能的通信框架。 2SpringCloudGateway的三大核心概念分别是路由、断言、过滤。 即根据url请求进行匹配到指定路由每个路由上面都有断言根据断言来判断是否可以进行路由最后对该url请求进行一个过滤例如监控、限流和日志输出等操作。1SpringCloudGateway的搭建需要先引入依赖然后创建主启动类最后配置Gateway的配置文件。- id属性值需要唯一uri的属性值即对应的服务器ip地址端口号predicates断言的属性值例如OrderController第一层RequestMapping注解的url属性值即判断url是否跟该值一致。 2服务网关Gateway通过Java API构建时需要实现RouteLocator接口构建路由规则。即先将yml文件中等价的gateway配置注释掉然后创建一个config配置类在配置类中创建一个方法使用RouteLocator接口来构建路由。并在该方法上添加Bean注解即由SpringIOC容器进行管理。 3SpringCloudGateway的动态路由功能即在yml文件中将原本路由的uri改成lb://服务提供者的微服务的名字然后需要引入EureakaClient和Gateway等依赖即可实现Gateway的动态路由功能。 也就是说需要配置和使用Eureka但是可以设置不把自身注册到Eureka服务中。 4SpringCloudGateway的路由断言路由断言分别有After、Before、Between、Cookie、Header、Host、Method、Query等。其中After、Before、Between都是跟时间有关的Cookie、Header、Host都是在头文件Headers中携带的参数Method是匹配指定的请求方法Query是在Params中检查参数的合法性。 断言是在YML文件的spring.cloud.gateway.routes.predicates下进行配置的。 5SpringCloudGateway的过滤器在用户访问各个服务前应在网关层统一做好鉴权、限流等工作。过滤器Filter的生命周期分为PRE和POST即PRE是路由之前POST是路由之后。它作用范围分为GatewayFilter和GlobalFilter即GatewayFilter是网关路由器是应用在单个路由或一个分组的路由上的而GlobalFilter是全局路由器会应用在所有路由上的。1内置过滤器即在YML文件中在filters下添加内置过滤器。 2自定义网关过滤器即需要创建一个配置类类名必须叫做XxxGatewayFilterFactory在该类上使用Component注解该类需要创建一个静态内部类Config里面的属性为配置文件中配置的参数必须继承AbstractGatewayFilterFactory重写shortcutFieldOrder()方法返回List参数列表为Config中属性集合创建无参构造方法方法体为super(Config.class)编写过滤逻辑 public GatewayFilter apply(Config config)。 3自定义全局过滤器当客户端第一次请求服务时服务端对用户进行信息认证登录认证通过将用户信息进行加密形成token返回给客户端作为登录凭证以后每次请求客户端都携带认证的token服务端对token进行解密判断是否有效有效则继续允许访问服务无效则不允许访问服务。 此时需要实现GlobalFilter, Ordered接口一个是全局过滤器接口一个是全局过滤器执行顺序的接口。 即在Ordered接口的实现类中返回一个数值该值越小当前过滤器的优先级越高。 GlobalFilter接口的实现类对请求参数进行一个过滤操作。1配置跨域即在yml文件配置允许跨域即可。 2JsonWebToken是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。适用于分布式的单点登录SSO。 客户端收到服务器返回的 JWT会把数据保存到loadStorage。JWT签证默认的算法是 HMAC SHA256HS256。 3用户登录并生成token返回的业务流程登录成功时JWT的Token通过JWTUtils工具类生成状态码为200消息为成功以及返回值的类型需要封装为Result实体类登录失败时不生成token状态码500消息为用户名或密码错误同时返回值的类型也是需要封装为Result实体类。此时该方法为Post请求因为涉及帐号信息。 并且需要引入JWT和fastjson依赖。使用gateway网关进行用户鉴权在application.yml文件中配置跳过login登录的鉴权即其他的url请求都要进行用户鉴权需要引入JWT和fastjson的依赖使用JWTUtils的生成token方法以及验证token是否有效的方法在用户鉴权时需要先获取跳过的路径进行匹配匹配成功则跳过鉴权进行下一步的业务操作匹配失败则说明该请求需要验证token首先需要从request请求的请求头中获取token如果token为空则返回一个response对象包含状态码和字符串消息如果token不为空则进行下一步验证即使用JWTUtils的token验证方法如果返回false则表示token无效或者失效则返回一个response对象包含状态码和字符串消息如果token不为空且token有效则进行下一步的业务操作。 即用户鉴权实际上就判断该请求是否需要跳过鉴权以及token是否为空和token是否有效的操作。