面试题答案
一键面试设计方案
- 使用JWT的过期时间机制:
- 在生成JWT时,设置合理的过期时间(exp字段)。例如,对于普通的用户认证JWT,设置较短的过期时间,如15分钟。这样即使JWT被截获并尝试重放,在过期后就无法使用。
- 各个依赖JWT认证的微服务在验证JWT时,严格检查exp字段,拒绝已过期的JWT。
- 引入随机数或一次性使用标识:
- 在生成JWT时,添加一个唯一的随机数(如UUID)作为自定义字段(例如
nonce
)。每个JWT的nonce
值都是不同的。 - 微服务端维护一个已使用
nonce
值的记录(可以使用缓存,如Redis)。当收到JWT进行认证时,首先检查nonce
是否已经在使用记录中。如果存在,则拒绝该JWT,表明这可能是一次重放攻击;如果不存在,则将该nonce
添加到使用记录中,并继续进行认证流程。
- 在生成JWT时,添加一个唯一的随机数(如UUID)作为自定义字段(例如
- 结合时间戳和滑动窗口机制:
- 在JWT中添加一个时间戳字段(例如
iat
,表示JWT的签发时间)。 - 微服务端设置一个时间滑动窗口,比如5分钟。当收到JWT时,检查
iat
字段,若iat
距离当前时间在滑动窗口内,则认为JWT是有效的,否则拒绝。这可以防止攻击者使用较旧的JWT进行重放攻击,同时也避免了因为时钟微小偏差导致的误判。
- 在JWT中添加一个时间戳字段(例如
关键实现点
- JWT生成:
- 在生成JWT的代码逻辑中,确保正确设置
exp
字段、nonce
字段(如果采用该方案)以及iat
字段。例如,在Java中使用JJWT库生成JWT时:
String nonce = UUID.randomUUID().toString(); Date now = new Date(); Date expiration = new Date(now.getTime() + 15 * 60 * 1000); // 15分钟过期 String token = Jwts.builder() .setSubject(user.getUsername()) .claim("nonce", nonce) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();
- 在生成JWT的代码逻辑中,确保正确设置
- JWT验证:
- 在各个微服务的JWT验证逻辑中,实现对
exp
、nonce
(如果采用该方案)和iat
字段的检查。以Java为例:
Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody(); Date expiration = claims.getExpiration(); if (expiration.before(new Date())) { throw new IllegalArgumentException("JWT已过期"); } String nonce = claims.get("nonce", String.class); if (redisTemplate.hasKey(nonce)) { throw new IllegalArgumentException("重放攻击检测:该JWT已使用"); } redisTemplate.opsForValue().set(nonce, "used", 15, TimeUnit.MINUTES); // 设置nonce使用记录并设置过期时间 Date issuedAt = claims.getIssuedAt(); long currentTime = System.currentTimeMillis(); if (currentTime - issuedAt.getTime() > 5 * 60 * 1000) { throw new IllegalArgumentException("JWT签发时间超出允许范围"); }
- 在各个微服务的JWT验证逻辑中,实现对
- 缓存管理:
- 如果采用
nonce
方案,要确保缓存(如Redis)的高可用性和性能。可以使用Redis集群来提高可用性,并且合理设置缓存的过期时间,避免缓存占用过多资源。例如,将nonce
在缓存中的过期时间设置为比JWT的exp
时间稍长,以保证在JWT有效期内能够检测到重放攻击。
- 如果采用