序文
Spring Cloud Gateway は Spring Boot 2 をベースにしており、Spring Cloud の新しいプロジェクトです。ゲートウェイは、リクエストを転送し、リクエストに対する横断的な懸念を提供するためのシンプルかつ効率的な方法を提供するように設計されています。
ゲートウェイは、クライアント リクエストをサーバー アプリケーションから分離するすべてのサービスのポータルに相当します。クライアント リクエストはゲートウェイを通過した後、定義されたルートとアサーションによって転送されます。ルートは、リクエストの転送先のアドレスを表します。アサーションはこれらのアドレスに対するリクエストと同等であり、条件が満たされる場合、ルーティングとアサーションの両方を満たしている場合にのみ転送されます。
このブログでは、ゲートウェイによる中国語文字化け問題の解決策、ログイン認証とゲートウェイによる認証の導入事例、ゲートウェイとセンチネルの統合方法を紹介します。
Gatew に関するその他の記事は次のとおりです。
Spring Cloud Gateway の学習 (1) - Gateway の基本概念と依存関係を導入する際の注意点 + グローバル ゲートウェイのソリューションと導入ユースケース
目次
導き出す
1. ゲートウェイによる中国語文字化け問題の解決策;
2. ログイン認証とゲートウェイによる認証の導入事例;
3. ゲートウェイとセンチネルの統合方法の紹介; 5. ゲートウェイとセンチネルの統合方法の紹介
ゲートウェイの中国語文字化け問題
文字化けする場合と文字化けしない場合の比較
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);
}
}
再度リクエストすると、応答結果は中国語で正常です
ゲートウェイは簡単なログインと権限を実装します
1.依存関係を導入する
<?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. 設定ファイル
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
3. グローバルフィルターを作成する
(1) エンコード形式の設定
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) ログイン認証フィルター
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) 許可フィルター
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 に保存されたデータ
4. カスタマイズされた戻りクラス
(1)HTTP応答
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) 列挙型 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;
}
}
ゲートウェイとセンチネルの統合
1.依存関係を導入する
<!-- 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. ゲートウェイ構成の追加
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.センチネルページ表示
リクエストを送信すると、2 つの監視ページが表示されます
4. フェイルファストパターン
高速障害モード、1 秒あたりの最大リクエスト数を設定します。
設定ファイルとセンチネルページパラメータの対応
迅速なリクエストを行い、監視トラフィックを制限しない
カスタム例外の戻り値
カスタム例外戻り設定の構成ファイル
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':'请求次数过多,被限流'}"
カスタム例外の効果を返す
5.APIモード
まずAPIを追加する必要があります
APIグループ化モードパラメータの選択
高速リクエスト、センチネルによって制限されています
要約する
1. ゲートウェイによる中国語文字化け問題の解決策;
2. ログイン認証とゲートウェイによる認証の導入事例;
3. ゲートウェイとセンチネルの統合方法の紹介; 5. ゲートウェイとセンチネルの統合方法の紹介