使用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 {@Overridepublic 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);}@Overridepublic 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的密钥和配置进行妥善管理。