Spring Cloud Gateway 基于 Spring Boot 2,是 Spring Cloud 的全新项目。Gateway 旨在提供一种简单而有效的途径来转发请求,并为它们提供横切关注点。
gateway相当于所有服务的门户,将客户端请求与服务端应用相分离,客户端请求通过gateway后由定义的路由和断言进行转发,路由代表需要转发请求的地址,断言相当于请求这些地址时所满足的条件,只有同时符合路由和断言才给予转发。
本篇博客介绍gateway的中文乱码问题解决方案,基于网关实现登陆认证和鉴权的案例,介绍gateway和sentinel整合的方式。
其他关于Gatew的文章如下:
Spring Cloud Gateway学习(1)—— Gateway 的基本概念 & 引入依赖需要注意的事项 +解决方案 & 全局网关的入门使用案例
1.gateway的中文乱码问题解决方案;
2.基于网关实现登陆认证和鉴权的案例;
3.介绍gateway和sentinel整合的方式;
乱码和不乱码的对比
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
package com.tianju.gateway.config;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 未起作用
*/
@Component
// 该值是可选的,表示Ordered接口中定义的订单值。值越低,优先级越高。
// 默认值为Ordered.LOWEST_PRECDENCE,表示最低优先级(输给任何其他指定的顺序值)
@Order(-1)
public class CharacterGateway implements GlobalFilter { // GatewayFilter
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
// HttpHeaders headers = response.getHeaders();
// headers.set(HttpHeaders.CONTENT_TYPE, "text/html;charset=UTF-8");
// headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
return chain.filter(exchange);
}
}
再次请求,响应结果中文正常
<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">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.tianjugroupId>
<artifactId>springcloud-restTemplateartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<groupId>com.tianju.gatewaygroupId>
<artifactId>springcloud-gatewayartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
dependencies>
project>
bootsrape.yml配置文件
spring:
cloud:
# nacos的部分
nacos:
discovery: # 注册中心
server-addr: http://192.168.111.130:8848/
register-enabled: true
# 命名空间
namespace: my-tianju
# 组名
group: DEV
# sentinel的配置
sentinel:
transport:
dashboard: 192.168.111.130:7777
port: 8719
# 这样一启动能够立马被发现,不用请求一次后才被监控
eager: true
# 自定义异常返回
scg:
fallback:
mode: response
response-body: "{'code':403.'msg':'请求次数过多,被限流'}"
# 网关部分
gateway:
discovery:
locator:
enabled: true # 允许定位
routes: # 路由
- id: springCloud-consumer # id要唯一
# lb表示负载均衡
uri: lb://springCloud-consumer # 在nacos里根据服务名称找
predicates:
# http://localhost:18888/hello-wx/api/consumer/addHello
# 根据上面,拼出来下面路径
# http://localhost:10002/api/consumer/addHello
- Path=/hello-wx/** # 比如输了 ip+端口/hello-wx/** 然后在nacos找真的路径
filters:
- StripPrefix=1 # 替换第一个,内置的filter过滤器
# 给头部添加token AddRequestHeader: 键为token,值为后面的
- AddRequestHeader=token, eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.s1dUJ5ffnO6ntO3WD3Je2Ythfb2qUK59_7tAS5ItXkY
- id: my-hello-baidu # id要唯一
uri: https://www.sohu.com
predicates:
# http://localhost:18888/hello-ly
- Path=/hello-ly/**
# 如要要用spring web,则把tomcat排除一下
# main:
# web-application-type: reactive
application.yml配置文件,redis等
server:
port: 18888
spring:
# redis的相关配置
redis:
host: 124.70.138.34
port: 6379
database: 0
password: My3927
# 应用名
application:
name: springCloud-gateway
logging:
level:
com.tianju.gateway: debug
package com.tianju.gateway.config;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* GatewayFilter未起作用,换成GlobalFilter后实现了,
* 并且@Order(-1)设置为-1
*/
@Component
// 该值是可选的,表示Ordered接口中定义的订单值。值越低,优先级越高。
// 默认值为Ordered.LOWEST_PRECDENCE,表示最低优先级(输给任何其他指定的顺序值)
@Order(-1)
public class CharacterGateway implements GlobalFilter { // GatewayFilter
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
// HttpHeaders headers = response.getHeaders();
// headers.set(HttpHeaders.CONTENT_TYPE, "text/html;charset=UTF-8");
// headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
return chain.filter(exchange);
}
}
package com.tianju.gateway.config;
import cn.hutool.jwt.JWTUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tianju.gateway.result.HttpResp;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
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.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
@Order(1)
@Slf4j
// http://localhost:18888/hello-wx/api/cinema/checkGenreInThisCinema
public class LoginGateway implements GlobalFilter {
@Override
@SneakyThrows
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("我是登陆过滤器>>>>>>>>>>");
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
// 请求request 的URL:http://localhost:18888/hello-wx/api/cinema/checkGenreInThisCinema
log.debug("请求request 的URL:"+request.getURI());
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.s1dUJ5ffnO6ntO3WD3Je2Ythfb2qUK59_7tAS5ItXkY
String token = request.getHeaders().getFirst("token");
log.debug("请求request 的token数据"+token);
if (ObjectUtils.isEmpty(token) || !JWTUtil.verify(token, "PET".getBytes())){ // 如果没有携带token 或者JWT没有通过
response.setStatusCode(HttpStatus.UNAUTHORIZED);
ObjectMapper objectMapper = new ObjectMapper();
DataBuffer buffer = response.bufferFactory()
.wrap(objectMapper.writeValueAsString(HttpResp.failed("未携带token"))
.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}else {
return chain.filter(exchange);
}
}
}
package com.tianju.gateway.config;
import cn.hutool.jwt.JWTUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tianju.gateway.result.HttpResp;
import lombok.SneakyThrows;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
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.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.nio.charset.StandardCharsets;
@Component
@Order(2)
@Slf4j
/**
* 鉴权的过滤器
*/
public class AuthGateway implements GlobalFilter {
@Autowired
private ReactiveStringRedisTemplate redisTemplate;
@Override
@SneakyThrows
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("我是鉴权的过滤器>>>>>>>>>>");
ServerHttpRequest request = exchange.getRequest();
// ServerHttpResponse response = exchange.getResponse();
// 鉴权
// 先获取用户信息目前用户演示指定userId=1(在实际在开过程中用户信息是从JWT中获取),
// 再获取path
// 请求request 的URL:http://localhost:18888/hello-wx/api/cinema/checkGenreInThisCinema
String path = request.getPath().toString();
log.debug("请求request 的URI:"+request.getURI());
log.debug("请求request 的PATH:"+path);
String uri = request.getURI().toString();
// redisTemplate.opsForHash().hasKey("1",path).map(
// b->{
// return chain.filter(exchange);
// }
// );
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.s1dUJ5ffnO6ntO3WD3Je2Ythfb2qUK59_7tAS5ItXkY
String token = request.getHeaders().getFirst("token");
log.debug("请求request 数据"+token);
return redisTemplate.opsForHash().hasKey("1", path).flatMap(b -> {
if (b) { // 放行
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// TODO:响应式编程中解决中文乱码问题
// response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
// 返回httpResp的相关
ObjectMapper objectMapper = new ObjectMapper();
DataBuffer buffer = null;
try {
buffer = response.bufferFactory()
.wrap(objectMapper.writeValueAsString(HttpResp.failed("没有权限"))
.getBytes(StandardCharsets.UTF_8));
} catch (JsonProcessingException e) {
return Mono.error(new RuntimeException(e));
}
return response.writeWith(Mono.just(buffer));
});
}
}
redis里面存放的数据
package com.tianju.gateway.result;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpResp<T> implements Serializable {
private int code;
private String msg;
private T data;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date date;
/**
* 成功无返回结果
*
* @return HttpResp
*/
public static <T> HttpResp<T> success() {
return success(null);
}
/**
* 成功, 有返回结果
*
* @param data 返回数据
* @return 返回HttpResp对象
*/
public static <T> HttpResp<T> success(T data) {
RespCode success = RespCode.SUCCESS;
return new HttpResp(
success.getCode(),
success.getMsg(),
data,
new Date());
}
/**
* 失败自定义返回错误信息
*
* @return 返回HttpResp对象
*/
public static <T> HttpResp<T> failed(String msg) {
return new HttpResp(
RespCode.ERROR.getCode(),
msg,
null,
new Date());
}
/**
* 失败
*
* @return 返回HttpResp对象
*/
public static <T> HttpResp<T> failed(RespCode respCode) {
return new HttpResp(
respCode.getCode(),
respCode.getMsg(),
null,
new Date());
}
}
package com.tianju.gateway.result;
import lombok.Getter;
@Getter
public enum RespCode {
SUCCESS(200, "ok"),
ERROR(500, "服务器异常"),
PARAM_IS_NULL(410, "请求必填参数为空"),
PARAM_ERROR(400, "用户请求参数错误"),
ACCESS_UNAUTHORIZED(301, "访问未授权"),
USER_NOT_EXIST(201, "用户不存在"),
USER_ACCOUNT_LOCKED(202, "用户账户被冻结"),
USER_ACCOUNT_INVALID(203, "用户账户已作废"),
USERNAME_OR_PASSWORD_ERROR(210, "用户名或密码错误"),
CLIENT_AUTHENTICATION_FAILED(212, "客户端认证失败"),
TOKEN_INVALID_OR_EXPIRED(230, "token无效或已过期"),
TOKEN_ACCESS_FORBIDDEN(231, "token已被禁止访问"),
RESOURCE_NOT_FOUND(404, "未找到接口异常");
;
private int code;
private String msg;
RespCode(int code,String msg){
this.code = code;
this.msg = msg;
}
}
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
spring:
cloud:
# nacos的部分
nacos:
discovery: # 注册中心
server-addr: http://192.168.111.130:8848/
register-enabled: true
# 命名空间
namespace: my-tianju
# 组名
group: DEV
# sentinel的配置
sentinel:
transport:
dashboard: 192.168.111.130:7777
port: 8719
# 这样一启动能够立马被发现,不用请求一次后才被监控
eager: true
# 网关部分
gateway:
discovery:
locator:
enabled: true # 允许定位
routes: # 路由
- id: springCloud-consumer # id要唯一
# lb表示负载均衡
uri: lb://springCloud-consumer # 在nacos里根据服务名称找
predicates:
# http://localhost:18888/hello-wx/api/consumer/addHello
# 根据上面,拼出来下面路径
# http://localhost:10002/api/consumer/addHello
- Path=/hello-wx/** # 比如输了 ip+端口/hello-wx/** 然后在nacos找真的路径
filters:
- StripPrefix=1 # 替换第一个,内置的filter过滤器
# 给头部添加token AddRequestHeader: 键为token,值为后面的
- AddRequestHeader=token, eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.s1dUJ5ffnO6ntO3WD3Je2Ythfb2qUK59_7tAS5ItXkY
- id: my-hello-baidu # id要唯一
uri: https://www.sohu.com
predicates:
# http://localhost:18888/hello-ly
- Path=/hello-ly/**
# 如要要用spring web,则把tomcat排除一下
# main:
# web-application-type: reactive
发送请求,sentinel页面出现两个
快速失败模式,设置每秒最大请求次数
配置文件里面和sentinel页面参数对应关系
进行快速请求,别sentinel限流
配置文件进行自定义异常返回设置
spring:
cloud:
# nacos的部分
nacos:
discovery: # 注册中心
server-addr: http://192.168.111.130:8848/
register-enabled: true
# 命名空间
namespace: my-tianju
# 组名
group: DEV
# sentinel的配置
sentinel:
transport:
dashboard: 192.168.111.130:7777
port: 8719
# 这样一启动能够立马被发现,不用请求一次后才被监控
eager: true
# 自定义异常返回
scg:
fallback:
mode: response
response-body: "{'code':403.'msg':'请求次数过多,被限流'}"
返回自定义异常的效果
首先需要新增一个api
api分组模式的参数选择
快速请求,被sentinel限流
1.gateway的中文乱码问题解决方案;
2.基于网关实现登陆认证和鉴权的案例;
3.介绍gateway和sentinel整合的方式;