使用JWT(JSON Web Token)和网关实现双令牌登录验证是一种安全性较高的方案。双令牌通常包括一个短期有效的访问令牌(access token)和一个长期有效的刷新令牌(refresh token)。以下是如何在Spring Boot项目中使用JWT和网关实现双令牌登录验证的步骤:
1. 设计令牌生成与验证策略:
- 访问令牌(Access Token):访问令牌是一个短期有效的JWT,它包含用户的身份信息和权限。访问令牌通常用于保护API端点,以确保只有授权用户才能访问。
- 刷新令牌(Refresh Token):刷新令牌是一个长期有效的令牌,它用于在访问令牌过期后获取新的访问令牌。刷新令牌通常比访问令牌更安全,因为它只用于刷新令牌,不用于访问API。
2. 引入依赖:
Maven依赖:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. 生成和验证JWT:
定义一个类,用于生成和验证JWT。可以使用JJWT库来处理JWT:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtils {
private static final String SECRET_KEY = "your-secret-key";
private static final long ACCESS_TOKEN_EXPIRY = 5 * 60 * 1000; // 5分钟
private static final long REFRESH_TOKEN_EXPIRY = 30 * 24 * 60 * 1000; // 30天
// 生成访问令牌
public static String generateAccessToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRY))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
// 生成刷新令牌
public static String generateRefreshToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRY))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
// 验证令牌
public static String verifyToken(String token) {
try {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (Exception e) {
return null; // 验证失败
}
}
}
这个类负责生成访问令牌和刷新令牌,以及验证令牌。
4. 在登录接口中使用JWT:
在登录接口中,验证用户的凭证(例如用户名和密码),如果验证成功,生成访问令牌和刷新令牌,并将它们返回给客户端。
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody LoginRequest loginRequest) {
// 验证用户凭证(例如用户名和密码)
// 假设验证通过
// 生成访问令牌和刷新令牌
String accessToken = JwtUtils.generateAccessToken(loginRequest.getUsername());
String refreshToken = JwtUtils.generateRefreshToken(loginRequest.getUsername());
// 返回令牌
Map<String, String> tokens = new HashMap<>();
tokens.put("accessToken", accessToken);
tokens.put("refreshToken", refreshToken);
return ResponseEntity.ok(tokens);
}
}
5. 在网关中验证访问令牌:
在Spring Cloud Gateway中使用自定义过滤器来验证访问令牌:
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从请求头中获取令牌
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 验证令牌
String username = JwtUtils.verifyToken(token);
if (username == null) {
// 令牌验证失败
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 令牌验证成功,继续处理请求
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1; // 设置过滤器优先级
}
}
这个过滤器会在请求到达网关时验证访问令牌。如果令牌无效或缺失,过滤器会返回HTTP 401 Unauthorized
响应;否则,继续处理请求。
6. 刷新令牌:
你可以提供一个接口,用于根据刷新令牌获取新的访问令牌。这通常是在访问令牌过期后客户端请求的。
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TokenController {
@PostMapping("/refresh")
public ResponseEntity<String> refresh(@RequestBody Map<String, String> request) {
String refreshToken = request.get("refreshToken");
String username = JwtUtils.verifyToken(refreshToken);
if (username == null) {
// 刷新令牌验证失败
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 生成新的访问令牌
String newAccessToken = JwtUtils.generateAccessToken(username);
return ResponseEntity.ok(newAccessToken);
}
}
总结:
通过以上步骤,你可以使用JWT和网关实现双令牌登录验证。该方案通过访问令牌保护API端点,通过刷新令牌获取新的访问令牌,提高了安全性和用户体验。在生产环境中,记得对JWT的密钥和配置进行妥善管理。