Token认证的基本原理
Token认证是一种无状态的认证机制,客户端在首次登录后获取一个Token,后续请求携带该Token进行身份验证。常见的Token类型包括JWT(JSON Web Token)。
JWT由三部分组成:Header、Payload和Signature,使用Base64URL编码后拼接而成。Header包含算法和类型信息,Payload包含用户数据,Signature用于验证Token的完整性。
JWT Token生成与验证
生成JWT Token需要使用密钥和指定算法(如HS256)。以下代码示例展示如何生成和验证JWT:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
public class JwtUtil {
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.signWith(SECRET_KEY)
.compact();
}
public static Claims validateToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
}
数字签名的工作原理
数字签名用于验证数据的完整性和来源真实性。发送方使用私钥对数据进行签名,接收方使用公钥验证签名。常见的算法包括RSA和ECDSA。
以下代码示例展示如何使用Java的Signature类进行数字签名:
import java.security.*;
import java.util.Base64;
public class DigitalSignature {
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
return keyGen.generateKeyPair();
}
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
byte[] digitalSignature = signature.sign();
return Base64.getEncoder().encodeToString(digitalSignature);
}
public static boolean verify(String data, String signature, PublicKey publicKey) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(data.getBytes());
return sig.verify(Base64.getDecoder().decode(signature));
}
}
WebAPI中的Token认证实现
在Spring Boot应用中,可以通过过滤器实现Token认证。以下示例展示如何创建JWT认证过滤器:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
try {
Claims claims = JwtUtil.validateToken(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(claims.getSubject(), null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
return;
}
}
filterChain.doFilter(request, response);
}
}
结合数字签名的Token增强
为了提高Token的安全性,可以结合数字签名技术。以下示例展示如何生成带有数字签名的Token:
public class SignedJwt {
private static KeyPair keyPair;
static {
try {
keyPair = DigitalSignature.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to generate key pair", e);
}
}
public static String generateSignedToken(String username) throws Exception {
String jwt = JwtUtil.generateToken(username);
String signature = DigitalSignature.sign(jwt, keyPair.getPrivate());
return jwt + "." + signature;
}
public static boolean verifySignedToken(String signedToken) throws Exception {
String[] parts = signedToken.split("\\.");
if (parts.length != 2) return false;
String jwt = parts[0];
String signature = parts[1];
return DigitalSignature.verify(jwt, signature, keyPair.getPublic());
}
}
实际应用中的安全考虑
Token应设置合理的过期时间,防止长期有效带来的安全风险。敏感操作应要求重新认证,即使Token仍然有效。
数字签名使用的私钥必须妥善保管,建议使用硬件安全模块(HSM)或密钥管理服务。公钥可以安全地分发给需要验证签名的客户端。
// 设置Token过期时间示例
public static String generateTokenWithExpiration(String username, long expirationMs) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(SECRET_KEY)
.compact();
}