Preface
Spring Cloud Gateway is based on Spring Boot 2 and is a new project of Spring Cloud. Gateway is designed to provide a simple and efficient way to forward requests and provide cross-cutting concerns for them.
The gateway is equivalent to the portal of all services, which separates client requests from server applications. After client requests pass through the gateway, they are forwarded by defined routes and assertions. The routes represent the addresses to which the requests need to be forwarded. The assertions are equivalent to the requests for these addresses. If the conditions are met, it will be forwarded only if it meets both routing and assertion.
This blog introduces gateway's solution to the Chinese garbled problem, a case of implementing login authentication and authentication based on gateway, and introduces the integration method of gateway and sentinel.
Other articles about Gatew are as follows:
Table of contents
lead out
1. Gateway’s solution to the problem of Chinese garbled characters;
2. A case of implementing login authentication and authentication based on gateway;
3. Introducing the integration method of gateway and sentinel;
Gateway Chinese garbled problem
Comparison between garbled and non-garbled characters
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
final solution
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);
}
}
Request again, the response result is normal in Chinese
Gateway implements simple login and permissions
1.Introduce dependencies
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tianju</groupId>
<artifactId>springcloud-restTemplate</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.tianju.gateway</groupId>
<artifactId>springcloud-gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 测试sql mapper 的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 响应式编程的 redis 的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- sentinel整合gateway-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
</dependencies>
</project>
2. Configuration file
bootsrape.yml configuration file
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 configuration file, redis, etc.
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
3. Write a global filter
(1) Set encoding format
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);
}
}
(2) Login authentication filter
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);
}
}
}
(3) Permission filter
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));
});
}
}
Data stored in redis
4. Customized return class
(1)HttpResp
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());
}
}
(2) Enumeration type RespCode
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;
}
}
Gateway and sentinel integration
1.Introduce dependencies
<!-- sentinel整合gateway-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
2. Add gateway configuration
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
3.sentinel page display
Send a request, two sentinel pages appear
4. Fail fast pattern
Fast failure mode, set the maximum number of requests per second
Correspondence between configuration file and sentinel page parameters
Make fast requests, don’t limit sentinel traffic
Custom exception return
Configuration file for custom exception return settings
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':'请求次数过多,被限流'}"
Return the effect of custom exception
5.API mode
First you need to add an api
API grouping mode parameter selection
Fast request, restricted by sentinel
Summarize
1. Gateway’s solution to the problem of Chinese garbled characters;
2. A case of implementing login authentication and authentication based on gateway;
3. Introducing the integration method of gateway and sentinel;