在现代 Web 应用程序中,安全性和用户体验是两个关键要素。使用 JWT(JSON Web Token) 进行用户认证已经成为一种流行的方式。在许多应用场景中,仅使用一个 access_token 可能会导致用户体验不佳,尤其是当 access_token 过期后。为此,使用 refresh_token 来续期 access_token 是一种非常有效的方案。
本文将通过详细的代码示例,介绍如何在 SpringBoot 项目中实现基于 JWT 的双 Token(access_token + refresh_token)授权和续期方案。
首先,在你的 SpringBoot 项目的 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
在 application.yml
中配置 JWT 的相关属性,例如密钥、过期时间等:
jwt:
secret: your_secret_key
access-token-expiration: 3600 # 1小时
refresh-token-expiration: 604800 # 1周
我们首先创建一个 JWT 工具类,用于生成和解析 token。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JWTUtils {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.access-token-expiration}")
private long accessTokenExpiration;
@Value("${jwt.refresh-token-expiration}")
private long refreshTokenExpiration;
public String generateAccessToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username, accessTokenExpiration);
}
public String generateRefreshToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username, refreshTokenExpiration);
}
private String createToken(Map<String, Object> claims, String subject, long expiration) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public String extractUsername(String token) {
return extractClaims(token).getSubject();
}
public boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
private Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
}
}
generateAccessToken
和 generateRefreshToken
方法分别生成 access_token 和 refresh_token。createToken
方法负责创建 JWT,设置声明、主题、发放时间和过期时间。extractUsername
和 isTokenExpired
方法用于提取 token 中的信息和检查 token 是否过期。我们需要实现一个简单的用户认证机制,以便在用户登录时生成并返回 access_token 和 refresh_token。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JWTUtils jwtUtils;
@PostMapping("/login")
public AuthResponse login(@RequestBody AuthRequest request) {
// 假设用户验证通过
String username = request.getUsername();
String accessToken = jwtUtils.generateAccessToken(username);
String refreshToken = jwtUtils.generateRefreshToken(username);
return new AuthResponse(accessToken, refreshToken);
}
}
class AuthRequest {
private String username;
private String password;
// Getters and Setters
}
class AuthResponse {
private String accessToken;
private String refreshToken;
public AuthResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
// Getters
}
AuthController
中创建了 /auth/login
接口,接收用户登录请求。在用户的 access_token 过期后,可以使用 refresh_token 来获取新的 access_token。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class RefreshTokenController {
@Autowired
private JWTUtils jwtUtils;
@PostMapping("/refresh")
public AuthResponse refresh(@RequestBody String refreshToken) {
String username = jwtUtils.extractUsername(refreshToken);
if (username != null && !jwtUtils.isTokenExpired(refreshToken)) {
String newAccessToken = jwtUtils.generateAccessToken(username);
return new AuthResponse(newAccessToken, refreshToken);
}
throw new RuntimeException("Refresh token is invalid or expired");
}
}
RefreshTokenController
中创建了 /auth/refresh
接口,用于刷新 access_token。为了保护我们的 API,我们需要在 Spring Security 中配置 JWT 认证。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/auth/**").permitAll() // 允许访问认证相关的接口
.anyRequest().authenticated(); // 其他请求需认证
}
}
通过本文的详细介绍,您已经掌握了如何在 SpringBoot 中实现基于 JWT 的双 Token(access_token + refresh_token)授权和续期方案。这种方案不仅提高了安全性,还增强了用户体验,使用户能够在 token 过期后无缝续期。
这种双 Token 的设计模式在许多现代 Web 应用中都有广泛的应用,如 RESTful API、微服务等,为用户提供了一种安全且高效的认证方式。