前言

Spring Cloud Gateway 基于 Spring Boot 2,是 Spring Cloud 的全新项目。Gateway 旨在提供一种简单而有效的途径来转发请求,并为它们提供横切关注点。

gateway相当于所有服务的门户,将客户端请求与服务端应用相分离,客户端请求通过gateway后由定义的路由和断言进行转发,路由代表需要转发请求的地址,断言相当于请求这些地址时所满足的条件,只有同时符合路由和断言才给予转发。

本篇博客介绍gateway的中文乱码问题解决方案,基于网关实现登陆认证和鉴权的案例,介绍gateway和sentinel整合的方式。

其他关于Gatew的文章如下:

Spring Cloud Gateway学习(1)—— Gateway 的基本概念 & 引入依赖需要注意的事项 +解决方案 & 全局网关的入门使用案例

在这里插入图片描述

引出


1.gateway的中文乱码问题解决方案;
2.基于网关实现登陆认证和鉴权的案例;
3.介绍gateway和sentinel整合的方式;

在这里插入图片描述

网关中文乱码问题

在这里插入图片描述

乱码和不乱码的对比

在这里插入图片描述

在这里插入图片描述

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)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)枚举类型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和sentinel整合

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.sentinel页面显示

发送请求,sentinel页面出现两个

在这里插入图片描述

4.快速失败的模式

快速失败模式,设置每秒最大请求次数

在这里插入图片描述

配置文件里面和sentinel页面参数对应关系

在这里插入图片描述

进行快速请求,别sentinel限流

在这里插入图片描述

自定义异常返回

配置文件进行自定义异常返回设置

在这里插入图片描述

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分组模式的参数选择

在这里插入图片描述

快速请求,被sentinel限流

在这里插入图片描述


总结

1.gateway的中文乱码问题解决方案;
2.基于网关实现登陆认证和鉴权的案例;
3.介绍gateway和sentinel整合的方式;

GitHub 加速计划 / sentine / Sentinel
19
6
下载
alibaba/Sentinel: Sentinel 是阿里巴巴开源的一款面向分布式服务架构的流量控制、熔断降级组件,提供实时监控、限流、降级和系统保护功能,适用于微服务治理场景。
最近提交(Master分支:4 个月前 )
195150bc * fix issue 2485 which occur oom when using async servlet request. * optimize imports * 1. fix the same issue in the webmvc-v6x 2. improve based on review comments 3 个月前
b78b09d3 3 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐