Spring Cloud Gateway Learning (2) - Gateway Chinese garbled solution & gateway-based login authentication and authentication case & gateway and sentinel integration case

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:

Spring Cloud Gateway Learning (1) - Basic concepts of Gateway & Things to note when introducing dependencies + Solutions & Introductory use cases of global gateways

Insert image description here

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;

Insert image description here

Gateway Chinese garbled problem

Insert image description here

Comparison between garbled and non-garbled characters

Insert image description here

Insert image description here

response.getHeaders().add("Content-Type","application/json;charset=UTF-8");

final solution

Insert image description here

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

Insert image description here

Gateway implements simple login and permissions

1.Introduce dependencies

Insert image description here

<?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

Insert image description here

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

Insert image description here

(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

Insert image description here

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

Insert image description here

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

Insert image description here

4. Customized return class

(1)HttpResp

Insert image description here

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

Insert image description here

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

Insert image description here

        <!--    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

Insert image description here

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

Insert image description here

4. Fail fast pattern

Fast failure mode, set the maximum number of requests per second

Insert image description here

Correspondence between configuration file and sentinel page parameters

Insert image description here

Make fast requests, don’t limit sentinel traffic

Insert image description here

Custom exception return

Configuration file for custom exception return settings

Insert image description here

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

Insert image description here

5.API mode

First you need to add an api

Insert image description here

API grouping mode parameter selection

Insert image description here

Fast request, restricted by sentinel

Insert image description here


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;

Guess you like

Origin blog.csdn.net/Pireley/article/details/133443833