Spring Cloud Gateway防重放攻击实现


一、实现防重放攻击的方案

  • 使用Nonce和Timestamp: 在请求中加入一个随机数(Nonce)和当前时间戳,服务端收到请求后验证该请求的Nonce是否已经使用过并且请求的时间戳是否在合理时间范围内。

  • 使用Token: 在服务端生成一个唯一的Token,并在响应中返回给客户端,客户端在下一次请求中需要带上该Token,服务端收到请求后验证该Token是否已经使用过。

  • 使用Redis 或者 JWT: 将每次请求的信息存储在Redis中或者JWT中,服务端收到请求后验证该请求是否重复。

二、使用Nonce和Timestamp进行防重放攻击

具体实现方式如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Clang
 * @version 1.0
 * @date 2023/1/17 14:01
 */
@Component
@Slf4j
public class ReplayAttackFilter implements GlobalFilter, Ordered {

    private static final long TIMESTAMP_VALID_TIME = 5 * 60 * 1000;

    private final Set<String> usedNonceSet = Collections.synchronizedSet(new HashSet<>());

    /**
     * 每分钟执行一次
     */
    @Scheduled(cron = "0 * * * * *")
    public void clearUsedNonceSet() {
        usedNonceSet.clear();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 从请求头中获取Nonce和Timestamp
        String nonce = exchange.getRequest().getHeaders().getFirst("nonce");
        String timestamp = exchange.getRequest().getHeaders().getFirst("Timestamp");
        // 验证Nonce和Timestamp是否合法
        if (validateNonceAndTimestamp(nonce, timestamp)) {
            // 如果合法,则放行请求
            return chain.filter(exchange);
        } else {
            // 如果不合法,则返回错误响应
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
    }

    @Override
    public int getOrder() {
        // 设置过滤器的优先级
        return -1;
    }

    private boolean validateNonceAndTimestamp(String nonce, String timestamp) {
        // 判断Nonce和Timestamp是否为空
        if (nonce == null || timestamp == null) {
            return false;
        }

        // 验证Nonce是否已经使用过
        if (usedNonceSet.contains(nonce)) {
            return false;
        } else {
            usedNonceSet.add(nonce);
        }

        // 验证Timestamp是否在合理时间范围内
        long timeStampValue;
        try {
            timeStampValue = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            return false;
        }

        long currentTime = System.currentTimeMillis();
        if (timeStampValue < currentTime - TIMESTAMP_VALID_TIME || timeStampValue > currentTime + TIMESTAMP_VALID_TIME) {
            return false;
        }

        return true;
    }
}

usedNonceSet是一个Set类型的变量,用来存储已经使用过的Nonce值。在上面的代码中,在验证请求的Nonce是否合法时,会先判断Nonce是否已经在usedNonceSet中出现过,如果出现过,则认为该请求是重放攻击,返回错误响应。

这个usedNonceSet变量应该是在类的变量里定义并初始化的,并且需要注意的是,这个set要确保线程安全,在不同的线程中能够正确的读写。

private final Set<String> usedNonceSet = Collections.synchronizedSet(new HashSet<>());

这里把usedNonceSet 使用了Collections.synchronizedSet 包装,可以保证线程安全。

需要注意的是,这种方法的限制是有时效性的,在一定时间内重复使用的nonce是不会被拦截的。因此需要定期清空这个set,或者使用其他方式存储已使用过的nonce。

   /**
     * 每分钟执行一次
     */
    @Scheduled(cron = "0 * * * * *")
    public void clearUsedNonceSet() {
        usedNonceSet.clear();
    }

需要注意的是,这些方法都只是简单的清空set,更好的做法是能够记录每个nonce使用的时间,在超过时效性之后删除。

private static final long TIMESTAMP_VALID_TIME = 5 * 60 * 1000;

这里定义了TIMESTAMP_VALID_TIME为5分钟,即请求时间戳的有效时间范围为当前时间前5分钟和当前时间后5分钟。

需要注意的是,这个TIMESTAMP_VALID_TIME是需要根据具体场景和需求来确定的,如果设置过大,则会增加重放攻击的风险,如果设置过小,则会增加误拦截的风险。

以上方法都是定时清空usedNonceSet的方法,可以根据业务需求和资源限制进行选择。

提示:更多内容可以访问Clang’s Blog:https://www.clang.asia

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个基于 Spring Cloud重放攻击的示例代码: 1.使用 JWT 验证: ```java public class JwtUtils { private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); private static final String SECRET_KEY = "YOUR_SECRET_KEY"; private static final long EXPIRATION_TIME = 86400 * 7; public static String createToken(String username) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { try { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } catch (Exception ex) { logger.error("Failed to parse JWT token: {}", ex.getMessage()); return null; } } } ``` 2.使用 Token Bucket 算法进行限流处理: ```java public class TokenBucket { private int bucketSize; private int tokens; private long lastRefillTime; public TokenBucket(int bucketSize, int tokens) { this.bucketSize = bucketSize; this.tokens = tokens; this.lastRefillTime = System.currentTimeMillis(); } public synchronized boolean tryConsume() { refill(); if (tokens > 0) { tokens--; return true; } return false; } private void refill() { long now = System.currentTimeMillis(); if (now > lastRefillTime) { int elapsedTime = (int) (now - lastRefillTime); int newTokens = elapsedTime / 1000; tokens = Math.min(tokens + newTokens, bucketSize); lastRefillTime = now; } } } ``` 3.使用缓存来存储已经处理过的请求: ```java public class CacheUtils { private static final int MAX_CACHE_SIZE = 1000; private static final int EXPIRATION_TIME = 5; // 缓存时间:5 秒 private static final LoadingCache<String, Boolean> CACHE = CacheBuilder.newBuilder() .maximumSize(MAX_CACHE_SIZE) .expireAfterWrite(EXPIRATION_TIME, TimeUnit.SECONDS) .build(new CacheLoader<String, Boolean>() { @Override public Boolean load(String key) throws Exception { return Boolean.TRUE; } }); public static boolean isRequestCached(String requestId) { return CACHE.getIfPresent(requestId) != null; } public static void cacheRequest(String requestId) { CACHE.put(requestId, Boolean.TRUE); } } ``` 4.使用 HTTPS 协议进行通信: 这部分不需要特别的代码实现,只需要配置好 SSL 证书即可。在 Spring Cloud 中,可以使用 Spring Boot 提供的自动配置来实现 SSL 配置,例如: ```yaml server: port: 443 ssl: key-store: classpath:ssl/keystore.p12 key-store-password: YOUR_PASSWORD key-store-type: PKCS12 key-alias: YOUR_ALIAS ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值