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;
    }
}

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

相关推荐
xuhaoyu_cpp_java28 分钟前
Spring学习(一)
java·经验分享·笔记·学习·spring
苍煜3 小时前
SpringBoot AOP切面编程精讲:实现方式、Spring区别及与自定义注解生产实战
java·spring boot·spring
zx2859634003 小时前
Laravel5.x版本革新特性全解析
mysql·gateway·智能路由器
流年似水~6 小时前
Java新手5分钟接AI:Spring AI Alibaba实战
java·人工智能·spring
jnrjian6 小时前
Library Cache Load Lock library cache pins are replaced by mutexes
java·后端·spring
952367 小时前
SpringAOP
java·后端·学习·spring
zx2859634008 小时前
Laravel6.x新特性全解析
java·后端·spring
未若君雅裁9 小时前
Spring Statemachine 实战入门:从零实现一个订单状态流转 Demo
java·spring·状态模式
java1234_小锋9 小时前
Spring AI 2.0 开发Java Agent智能体 - Advisors —— 拦截器模式增强AI能力
java·人工智能·spring·ai·spring ai2.0
苍煜10 小时前
SpringBoot Spring事务完整版详解:@Transactional注解实操 + 七大事务传播机制用法
spring boot·spring·oracle