Spring Cloud Gateway Learning (2) - ゲートウェイの中国語文字化けソリューション、ゲートウェイベースのログイン認証と認証のケース、ゲートウェイとセンチネルの統合のケース

序文

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. ゲートウェイとセンチネルの統合方法の紹介

おすすめ

転載: blog.csdn.net/Pireley/article/details/133443833