spring cloud之gateway和JWT回顾

最开始学习时,没怎么用,只知道它是网关,当时因为经常使用Nginx做网关,慢慢就淡忘了,最近为了代码整合性,就使用它,非常棒。关于JWT以前也使用,后面调用基本以第三方接口开发的比较多,当时自己的服务,添加自己的规则生成token

整合gateway网关

1、pom

xml 复制代码
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>com.xl</groupId>
            <artifactId>auth-service-api</artifactId>
            <version>${project.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

其中auth-service-api和redis后面会用到,当前用不到

2、启动类注解

java 复制代码
// 没有什么特别的
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients

启动程序后,直接访问http://localhost:25200/actuator,寻找gateway,在访问/actuator/gateway,c此时会报错404,请求错误,我们要看路由,加上/routes,查看json,大概就知道怎么访问了,http://localhost:25200/feign-client/sayHi,如果是多个feign-client服务,自动就负载均衡了,是不是比nginx方便多了。

3、配置文件

yaml 复制代码
spring:
  application:
    name: gateway-sample
  redis:
    host: localhost
    port: 6379
    database: 0
 # 开始配置
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true # FEIGN-CLIENT可以小写访问
      routes:
      - id: yml
        uri: lb://FEIGN-CLIENT
        predicates:
        - Path=/yml/**
        filters:
        - StripPrefix=1
   # 结束配置
   # 下面这里是限流配置
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@remoteAddrKeyResolver}'
            redis-rate-limiter.replenishRate: 1 # 每秒填充令牌
            redis-rate-limiter.burstCapacity: 1 # 总令牌,举个例子:假设现在令牌是满的,也就是总令牌20个,每秒填充为10,总共20,当前请求数为9,第一秒时,处理了9个,还剩11个,此时每秒会填充10个,此时有21个,但由于限制总令牌为20个,此时就是20个,第二秒时同理,如果每秒30个,第一秒会限流10个,报429,第二秒会限流20个,报429。

server:
  port: 25200
management:
  cloudfoundry:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:15001/eureka,http://localhost:15002/eureka

这里就可以直接将feign-client替换成yml,http://localhost:25200/yml/sayHi

4、配置文件配置

java 复制代码
package com.xl.gateway;

import com.xl.gateway.resop.ErrorFilter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.HttpMessageConverter;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;

@Configuration
public class GatewayConfiguration {

    @Autowired
    private GatewayFilterIn gatewayFilterIn;
    @Autowired
    private AuthFilter authFilter;
    @Autowired
    private ErrorFilter errorFilter;

    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }


    @Bean
    @Order
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> {
                    return r.path("/java/**")
                            .and().method(HttpMethod.GET)
                            .and().header("name")
                            .filters(f -> {
                                return f.stripPrefix(1)
                                        .addResponseHeader("der", "wer")
                                        .filter(errorFilter)
                                        .filter(authFilter)
                                        // 限流规则
                                        .requestRateLimiter(config -> config
                                                .setKeyResolver(this.remoteAddrKeyResolver())
                                                .setRateLimiter(this.redisRateLimiter())
                                        )
                                        ;
                            })
                            .uri("lb://FEIGN-CLIENT");
                })
                .route(r -> {
                    return r.path("/seckill/**")
                            .and().between(time("2025-01-15 17:30:00"), time("2025-01-15 17:35:00"))
                            .filters(f -> {
                                return f.stripPrefix(1);
                            })
                            .uri("lb://FEIGN-CLIENT");
                })
                .build();
    }

// 时间处理
    public static ZonedDateTime time(String dateTimeStr) {
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 解析字符串为 LocalDateTime
        LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, formatter);
        // 转换为 ZonedDateTime
        return ZonedDateTime.of(localDateTime, ZoneId.of("UTC"));
    }

// 限流配置
    @Bean
    public KeyResolver remoteAddrKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getRemoteAddress().getHostName());
    }

// 限流规则
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        // 这里可以设置 RedisRateLimiter 的参数,如 replenishRate、burstCapacity 和 requestedTokens
        return new RedisRateLimiter(1, 1, 1);
    }
}

这里其实和配置文件大差不差

JWT使用

1、pom

XML 复制代码
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.1</version>
        </dependency>

2、程序

java 复制代码
package com.xl.auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Date;

@Slf4j
@Service
public class JwtService {

    private static final String KEY = "xl";
    private static final String ISSUE = "ligang";
    private static final Long EXPIRE_TIME = 60000L;
    private static final String USER_NAME = "username";

    public String token(Account account) {
        Algorithm algorithm = Algorithm.HMAC256(KEY);
        Date date = new Date();
        String sign = JWT.create()
                .withIssuer(ISSUE)
                .withIssuedAt(date)
                .withExpiresAt(new Date(date.getTime() + EXPIRE_TIME))
                .withClaim(USER_NAME, account.getUsername())
                .sign(algorithm);
        log.info("jwt generated user:{}", account.getUsername());
        return sign;
    }

    public Boolean verify(String token, String username) {
        log.info("verifying jwt - username:{}", username);
        try {
            Algorithm algorithm = Algorithm.HMAC256(KEY);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(ISSUE)
                    .withClaim(USER_NAME, username)
                    .build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            log.error("auth failed", e);
            return false;
        }
    }


}

后面自己对应的接口,比如登录获取token,验证token,续约token等

gateway添加规则使用JWT验证token

java 复制代码
package com.xl.gateway;

import com.xl.auth.AuthResponse;
import com.xl.auth.AuthService;
import com.xl.auth.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.http.HttpHeaders;
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.StopWatch;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
@Component("authFilter")
public class AuthFilter implements GatewayFilter, Ordered {

    private static final String AUTH = "Aothorzation";
    private static final String USERNAME = "user-name";

    @Autowired
    private AuthService authService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("Auth start");
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = request.getHeaders();
        String token = header.getFirst(AUTH);
        String username = header.getFirst(USERNAME);

        ServerHttpResponse response = exchange.getResponse();
        if (StringUtils.isBlank(token)) {
            log.error("token not found");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        AuthResponse resp = authService.verify(token, username);
        if (resp.getCode() != 1L) {
            log.error("invalid token");
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header("user-name", username);
        ServerHttpRequest build = mutate.build();

        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("user-name", username);
        return chain.filter(
                exchange.mutate()
                        .request(build)
                        .response(response)
                        .build());
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

还有很多规则,看官方文档或者看代码源码研究,就摸索出来了,还是很有用的

相关推荐
mqiqe1 小时前
Spring AI TikaDocumentReader
人工智能·spring·知识图谱
橘子海全栈攻城狮3 小时前
【源码+文档+调试讲解】基于Spring Boot的协作会话平台
java·spring boot·后端·spring·微信小程序·小程序
m0_748252234 小时前
IP地址、子网掩码(NETMASK)和网关(Gateway)
tcp/ip·gateway·智能路由器
Icoolkj4 小时前
微服务学习-Gateway 统一微服务入口
学习·微服务·gateway
weixin_SAG14 小时前
14天学习微服务-->第2天:Spring Cloud深入与实践
学习·spring cloud·微服务
longze_718 小时前
计算机基础问答-面试总结
java·python·计算机网络·spring·面试·c#
zzyh1234561 天前
搭建springcloud脚手架
后端·spring·spring cloud
我要学编程(ಥ_ಥ)1 天前
初始JavaEE篇 —— 快速上手 SpringBoot
java·spring boot·spring·java-ee
小菜日记^_^1 天前
苍穹外卖项目总结(二)
java·spring boot·spring·tomcat·maven·mybatis·postman