Prefácio
Spring Cloud Gateway é baseado no Spring Boot 2 e é um novo projeto do Spring Cloud. O Gateway foi projetado para fornecer uma maneira simples e eficiente de encaminhar solicitações e fornecer preocupações transversais a elas.
O gateway é equivalente ao portal de todos os serviços, que separa as solicitações dos clientes das aplicações do servidor. Depois que as solicitações dos clientes passam pelo gateway, elas são encaminhadas por rotas e asserções definidas. As rotas representam os endereços para os quais as solicitações precisam ser encaminhadas. As asserções são equivalentes às solicitações para esses endereços. Se as condições forem atendidas, ele será encaminhado somente se atender ao roteamento e à asserção.
Este blog apresenta a solução de gateway para o problema ilegível chinês, um caso de implementação de autenticação de login e autenticação baseada em gateway, e apresenta o método de integração de gateway e sentinela.
Outros artigos sobre Gatew são os seguintes:
Índice
liderar
1. Solução do gateway para o problema dos caracteres chineses distorcidos;
2. Um caso de implementação de autenticação de login e autenticação baseada em gateway;
3. Introdução do método de integração de gateway e sentinela;
Problema distorcido do gateway chinês
Comparação entre caracteres ilegíveis e não ilegíveis
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
solução final
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);
}
}
Solicite novamente, o resultado da resposta é normal em chinês
Gateway implementa login e permissões simples
1. Introduzir dependências
<?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. Arquivo de configuração
arquivo de configuração 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
arquivo de configuração application.yml, 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. Escreva um filtro global
(1) Definir formato de codificação
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) Filtro de autenticação de login
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) Filtro de permissão
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));
});
}
}
Dados armazenados em redis
4. Classe de devolução personalizada
(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) Tipo de enumeração 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;
}
}
Integração de gateway e sentinela
1. Introduzir dependências
<!-- 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. Adicionar configuração de gateway
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. exibição da página sentinela
Envie uma solicitação, duas páginas sentinela aparecem
4. Padrão rápido de falha
Modo de falha rápido, defina o número máximo de solicitações por segundo
Correspondência entre o arquivo de configuração e os parâmetros da página sentinela
Faça solicitações rápidas, não limite o tráfego sentinela
Retorno de exceção personalizado
Arquivo de configuração para configurações de retorno de exceção customizadas
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':'请求次数过多,被限流'}"
Retornar o efeito da exceção personalizada
5.Modo API
Primeiro você precisa adicionar uma API
Seleção de parâmetros do modo de agrupamento de API
Solicitação rápida, restrita por sentinela
Resumir
1. Solução do gateway para o problema dos caracteres chineses distorcidos;
2. Um caso de implementação de autenticação de login e autenticação baseada em gateway;
3. Introdução do método de integração de gateway e sentinela;