Spring Boot中jwt的使用
记录一下现在我是怎么实现jwt的。为什么说是现在呢?说实话这是我第三次在Spring Boot里添加JWT,但是每次添加的方式和代码因为查阅的资料不同,实现的方式也不太一样。当然jwt终究是jwt,大同小异。
我用的版本比较新,所以很多方法都被标注了过期,我也没有去深入研究最新的代码究竟应该怎么实现。
Spring Boot版本是3.3.5, jjwt用的0.12.6。
引入依赖
用的是jjwt
实现jwt,spring-security
实现拦截。
在pom.xml里添加:
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.12.6</version> </dependency>
|
JwtUtil 文件
这是为了实现jwt
而创建的工具类,建议放在项目文件夹的utils
文件夹里,和controller
文件夹同级。代码中的jwt.secret
,jwt.expiration
是写在application.properties
里的变量。
1 2
| jwt.secret=XXXXXXXXXXXXX jwt.expiration=604800000
|
这里的jwt.secret
必须满足一定的要求:HS256 算法要求签名密钥的大小至少为 256 位(即 32 字节)。并且写在文件里的是用Base64加密过的。Base64 编码的目的是让二进制密钥在传输和存储时更加安全和兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package com.airomance.easytravelroute.utils;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date;
@Component public class JwtUtil { @Value("${jwt.secret}") private String secretKey;
@Value("${jwt.expiration}") private long expirationTime;
private SecretKey getSecretKey() { byte[] decodedKey = Base64.getDecoder().decode(secretKey); return new SecretKeySpec(decodedKey, 0, decodedKey.length, "HmacSHA256"); }
public String generateToken(String username){ return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expirationTime)) .signWith(getSecretKey()) .compact();
} public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); }
private <T> T extractClaim(String token, ClaimsResolver<T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.resolve(claims); }
private Claims extractAllClaims(String token) { return Jwts.parser() .setSigningKey(getSecretKey()) .build() .parseSignedClaims(token) .getPayload(); }
public boolean validateToken(String token, String username) { return (username.equals(extractUsername(token)) && !isTokenExpired(token)); }
private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); }
private Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); }
@FunctionalInterface private interface ClaimsResolver<T> { T resolve(Claims claims); }
}
|
JwtAuthenticationFilter 文件
JWT 认证过滤器 (JwtAuthenticationFilter),用于在每次 HTTP 请求时检查 JWT(JSON Web Token)是否有效,并在验证成功后设置用户身份认证信息。它是基于 Spring Security 的 OncePerRequestFilter 类,用于确保每个请求只被过滤一次。
- 检查请求中的 JWT Token。
- 验证 Token 的有效性和格式。
- 如果 Token 有效,将用户信息设置到 Spring Security 的上下文中。
- 如果请求在白名单路径中,则跳过 JWT 检查。
- 如果 Token 无效或缺失,返回 HTTP 401 Unauthorized 错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| package com.airomance.easytravelroute.filter;
import com.airomance.easytravelroute.utils.JwtUtil; import io.jsonwebtoken.JwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.util.logging.Logger; import java.util.logging.Level;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private static final Logger logger = Logger.getLogger(JwtAuthenticationFilter.class.getName()); private static final String[] WHITE_LIST_PATHS = {"/users/loginByEmail"};
public JwtAuthenticationFilter(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; }
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isWhiteListed(request)) { filterChain.doFilter(request, response); return; } String token = getJwtFromRequest(request);
if (token != null) { try { if (jwtUtil.validateToken(token, jwtUtil.extractUsername(token))) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUtil.extractUsername(token), null, null); SecurityContextHolder.getContext().setAuthentication(authentication); } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid or expired JWT token"); return; } } catch (JwtException e) { logger.log(Level.SEVERE, "Your error message here", e); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid JWT token format"); return; } }else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("JWT token is missing"); return; }
filterChain.doFilter(request, response); }
private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } private boolean isWhiteListed(HttpServletRequest request) { String requestURI = request.getRequestURI(); for (String path : WHITE_LIST_PATHS) { if (requestURI.equals(path)) { return true; } } return false; } }
|
SecurityConfig文件
Spring Security 的配置类,用于定义应用的安全策略,包括认证机制、过滤器链、密码加密方式等。它通过 SecurityFilterChain 自定义安全规则,并使用 JwtAuthenticationFilter 来实现基于 JWT 的认证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.airomance.easytravelroute.config;
import com.airomance.easytravelroute.filter.JwtAuthenticationFilter; import com.airomance.easytravelroute.utils.JwtUtil; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration public class SecurityConfig {
private final JwtUtil jwtUtil;
public SecurityConfig(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; }
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class) .authorizeRequests(authorizeRequests -> authorizeRequests .requestMatchers("/users/loginByEmail").permitAll() .anyRequest().authenticated() ) .csrf(csrf -> csrf.disable()); return http.build(); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
|
缺点和不足
现在可以看到白名单需要在两个文件里都写一遍,原因是我想要在需要token的时候如果没有tokne的请求返回”JWT token is missing”,但是我发现JwtAuthenticationFilter
如果不加入白名单判断,就会把所有没有token的都拦截了。
而且没有暂时没有加入角色控制,因为相关接口还没有写。