Spring Cloud Gateway的使用

介绍

spring cloud gateway是spring cloud的一个api网关组件,替换Zuul开发的网关服务,基于Spring5.0 + SpringBoot2.0 + WebFlux(基于性能的Reactor模式响应式通信框架Netty,异步阻塞模型)等技术开发,性能高于Zuul,旨在为微服务架构提供种简单有效的统的API路由管理式。

调用流程

PS:此处借用一张网上的流程图

可以看到,接收到请求后,会先去遍历配置中的routes,匹配路由信息,判断当前路由是否可用,匹配成功后,会进入过滤器,在过滤器中可以进行各种操作,本文就是在实现WebFilter过滤器,在其中进行接口权限校验等等,通过过滤器后,就开始转发请求到相应的服务当中。

关键实现

具体项目实现请看:项目地址

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gcp</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>gateway</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--服务注册-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--gateway网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

application.yml(配置)

server:
  port: 8700
spring:
  application:
    name: gateway
  profiles:
    active: dev
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.56.1:8848
        group: DEFAULT_GROUP
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
          enabled: true
      routes:
          # id,自定义,不可重复
        - id: security_route
          # 转发服务名,nacos中待调用的服务名称
          uri: lb://security
          # 路由规则
          predicates:
            - Path=/security/**
          filters:
            - StripPrefix=1

AuthorizeFilter.class(权限校验过滤器,实现WebFilter)

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gcp.gateway.config.RedisCache;
import com.gcp.gateway.response.CommonException;
import com.gcp.gateway.util.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;

/**
 * 权限校验过滤器
 * @author gcp
 */
@Order(1)
@Component
@Slf4j
public class AuthorizeFilter implements WebFilter {

    @Resource
    private RedisCache redisCache;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        URI uri = exchange.getRequest().getURI();
        log.info("开始校验路由:{}",uri.getPath());
        if(uri.getPath().contains("login")){
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst("token");
        log.info("接收的token为:{}",token);
        if(!StringUtils.hasText(token)){
            log.error("token为空");
            throw new CommonException("权限不足,请重新登录");
        }
        String userId;
        // 解析token
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            log.error("解析token失败");
            throw new CommonException("解析token失败");
        }
        // 从redis中获取用户信息
        String redisKey = "login:" + userId;
        redisCache.getCacheObject(redisKey);
        JSONObject jsonObject = redisCache.getCacheObject(redisKey);
        if (jsonObject.isEmpty()) {
            log.error("用户信息获取失败");
            throw new CommonException("账户过期,请重新登录");
        }
        String roleId = exchange.getRequest().getHeaders().getFirst("role_id");
        List<String> list = JSONArray.parseArray(jsonObject.get("permissions").toString(),String.class);
        // 校验是否有该角色
        if (!list.contains(roleId)) {
            log.error("角色不匹配");
            throw new CommonException("权限不足");
        }
        String url = uri.getPath();
        String realUrl;
        realUrl = url.split("\\/")[1];
        String role = redisCache.getCacheObject("role:role_" + roleId);
        // 校验角色是否存在该路径
        if (!role.contains(url.split(realUrl)[1])) {
            log.error("该接口路径无权限");
            throw new CommonException("权限不足!!");
        }
        return chain.filter(exchange);
    }
}

WebExceptionHandler.class(拦截filter中的自定义异常,并抛出响应状态码)

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gcp.gateway.response.CommonException;
import com.gcp.gateway.response.ResponseCode;
import com.gcp.gateway.response.ResponseModelDto;
import com.gcp.gateway.response.ResponseModels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
 * filter异常响应
 * @author gcp
 */
@Slf4j
@Order(-1)
@Component
public class WebExceptionHandler implements ErrorWebExceptionHandler {

    @Resource
    ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();

        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        response.setStatusCode(HttpStatus.OK);

        ResponseModelDto res;
        if (ex instanceof CommonException) {
            log.debug("接口调用失败,url={},message={}", exchange.getRequest().getURI().toString(), ex.getMessage());
            res = ResponseModels.commonException((CommonException)ex);
        } else {
            log.error("接口调用失败,url={},message={}", exchange.getRequest().getURI().toString(), ex.getMessage());
            res = ResponseModels.commonException().message(ResponseCode.CommonException);
        }
        HttpHeaders headers = response.getHeaders();
        headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
        headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*");
        headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        headers.set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
        headers.set(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
        headers.setContentType(MediaType.APPLICATION_JSON);
        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();

            try {
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(res));
            } catch (JsonProcessingException e) {
                log.warn("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Cloud Gateway是一个基于Spring 5.0、Spring Boot 2.0和Project Reactor等技术开发的API网关,它的作用是保护、增强和控制对于API服务的访问。 在使用Spring Cloud Gateway时,首先需要搭建网关项目。在网关项目的配置文件(application.yml)中,我们可以配置多个路由(routes),每个路由都有一个自定义的id和目标服务的URI。通过断言(predicates)来确定路由的条件,例如路径(path)。 例如,我们可以使用如下配置来创建一个路由到订单服务(Spring Cloud Order Service Provider)的路径"/order/**",并将请求转发到具体的订单服务实例。同样地,我们可以创建另一个路由到用户服务(Spring Cloud User Service Consumer)的路径"/user/**"。 除了路由配置,我们同样需要配置Eureka注册中心的地址,以便网关能够获取服务列表。可以通过配置eureka.client.service-url来指定Eureka Server的地址。 另外,我们还可以为网关配置多个profile,通过不同的端口启动不同的实例。例如可以配置两个profile(g1和g2),分别在8020和8021端口启动网关实例。 通过以上步骤,我们就可以成功使用Spring Cloud Gateway来管理和控制API服务的访问了。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SpringCloudGateway使用篇](https://blog.csdn.net/yuanshangshenghuo/article/details/107101640)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SpringCloud gateway使用](https://blog.csdn.net/m0_37044584/article/details/115302516)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☆叙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值