From 1b96ce8a7321b804d9e91399b42fd5d4402c09b1 Mon Sep 17 00:00:00 2001 From: saa99999 Date: Sun, 17 May 2026 22:05:53 +0800 Subject: [PATCH] Fix hardcoded JWT secret key in application.yml ??use env var with startup validation (CWE-798) Replaced the hardcoded jwt.secret with ${JWT_SECRET:} placeholder and added @PostConstruct validation that rejects empty, known-default, and short keys. The application now refuses to start with a clear error message if the JWT secret is not properly configured. --- .../java/com/bfwg/security/TokenHelper.java | 283 ++++++++++-------- src/main/resources/application.yml | 16 +- 2 files changed, 160 insertions(+), 139 deletions(-) diff --git a/src/main/java/com/bfwg/security/TokenHelper.java b/src/main/java/com/bfwg/security/TokenHelper.java index 28ec6a2..2e526b9 100644 --- a/src/main/java/com/bfwg/security/TokenHelper.java +++ b/src/main/java/com/bfwg/security/TokenHelper.java @@ -1,132 +1,153 @@ -package com.bfwg.security; - -import java.util.Date; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -import com.bfwg.common.TimeProvider; -import com.bfwg.model.User; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import io.jsonwebtoken.security.MacAlgorithm; -import jakarta.servlet.http.HttpServletRequest; - -/** - * Created by fan.jin on 2016-10-19. - */ - -@Component -public class TokenHelper { - - @Value("${app.name}") - private String APP_NAME; - - @Value("${jwt.secret}") - public String SECRET; - - @Value("${jwt.expires_in}") - private int EXPIRES_IN; - - @Value("${jwt.header}") - private String AUTH_HEADER; - - @Autowired - TimeProvider timeProvider; - - private MacAlgorithm SIGNATURE_ALGORITHM = Jwts.SIG.HS512; - - public String getUsernameFromToken(String token) { - String username; - try { - final Claims claims = this.getAllClaimsFromToken(token); - username = claims.getSubject(); - } catch (Exception e) { - username = null; - } - return username; - } - - public Date getIssuedAtDateFromToken(String token) { - Date issueAt; - try { - final Claims claims = this.getAllClaimsFromToken(token); - issueAt = claims.getIssuedAt(); - } catch (Exception e) { - issueAt = null; - } - return issueAt; - } - - public String refreshToken(String token) { - String refreshedToken; - Date a = timeProvider.now(); - try { - final Claims claims = getAllClaimsFromToken(token); - refreshedToken = Jwts.builder().claims(claims).issuedAt(a).expiration(generateExpirationDate()) - .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); - } catch (Exception e) { - refreshedToken = null; - } - return refreshedToken; - } - - public String generateToken(String username) { - return Jwts.builder().issuer(APP_NAME).subject(username).issuedAt(timeProvider.now()) - .expiration(generateExpirationDate()) - .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); - } - - private Claims getAllClaimsFromToken(String token) { - Jws claims; - try { - claims = Jwts.parser().verifyWith(Keys.hmacShaKeyFor(SECRET.getBytes())).build().parseSignedClaims(token); - } catch (Exception e) { - claims = null; - } - return claims.getPayload(); - } - - private Date generateExpirationDate() { - long expiresIn = EXPIRES_IN; - return new Date(timeProvider.now().getTime() + expiresIn * 1000); - } - - public int getExpiredIn() { - return EXPIRES_IN; - } - - public Boolean validateToken(String token, UserDetails userDetails) { - User user = (User) userDetails; - final String username = getUsernameFromToken(token); - final Date created = getIssuedAtDateFromToken(token); - return (username != null && username.equals(userDetails.getUsername()) - && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())); - } - - private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { - return (lastPasswordReset != null && created.before(lastPasswordReset)); - } - - public String getToken(HttpServletRequest request) { - /** - * Getting the token from Authentication header e.g Bearer your_token - */ - String authHeader = getAuthHeaderFromHeader(request); - if (authHeader != null && authHeader.startsWith("Bearer ")) { - return authHeader.substring(7); - } - - return null; - } - - public String getAuthHeaderFromHeader(HttpServletRequest request) { - return request.getHeader(AUTH_HEADER); - } - +package com.bfwg.security; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import com.bfwg.common.TimeProvider; +import com.bfwg.model.User; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.MacAlgorithm; +import jakarta.servlet.http.HttpServletRequest; + +/** + * Created by fan.jin on 2016-10-19. + */ + +@Component +public class TokenHelper { + + @Value("${app.name}") + private String APP_NAME; + + @Value("${jwt.secret}") + private String SECRET; + + @Value("${jwt.expires_in}") + private int EXPIRES_IN; + + @Value("${jwt.header}") + private String AUTH_HEADER; + + @Autowired + TimeProvider timeProvider; + + private MacAlgorithm SIGNATURE_ALGORITHM = Jwts.SIG.HS512; + + private static final java.util.Set KNOWN_WEAK_SECRETS = java.util.Set.of( + "", "secret", "changeme", "jwt_secret", "mySecretKey", + "p2H(++:$j*xKfKvK6n5k=},@@]CWkbj7d0iQ%6/;0}x6?[wNWR,$=0/HHx*&pJAq" + ); + + @jakarta.annotation.PostConstruct + public void validateSecret() { + if (SECRET == null || KNOWN_WEAK_SECRETS.contains(SECRET)) { + throw new IllegalStateException( + "jwt.secret is not set or uses a known default value. " + + "Set it via the JWT_SECRET environment variable. " + + "Generate a secure key: openssl rand -base64 64" + ); + } + if (SECRET.length() < 32) { + throw new IllegalStateException( + "jwt.secret must be at least 32 characters. Current length: " + SECRET.length() + ); + } + } + + public String getUsernameFromToken(String token) { + String username; + try { + final Claims claims = this.getAllClaimsFromToken(token); + username = claims.getSubject(); + } catch (Exception e) { + username = null; + } + return username; + } + + public Date getIssuedAtDateFromToken(String token) { + Date issueAt; + try { + final Claims claims = this.getAllClaimsFromToken(token); + issueAt = claims.getIssuedAt(); + } catch (Exception e) { + issueAt = null; + } + return issueAt; + } + + public String refreshToken(String token) { + String refreshedToken; + Date a = timeProvider.now(); + try { + final Claims claims = getAllClaimsFromToken(token); + refreshedToken = Jwts.builder().claims(claims).issuedAt(a).expiration(generateExpirationDate()) + .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); + } catch (Exception e) { + refreshedToken = null; + } + return refreshedToken; + } + + public String generateToken(String username) { + return Jwts.builder().issuer(APP_NAME).subject(username).issuedAt(timeProvider.now()) + .expiration(generateExpirationDate()) + .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); + } + + private Claims getAllClaimsFromToken(String token) { + Jws claims; + try { + claims = Jwts.parser().verifyWith(Keys.hmacShaKeyFor(SECRET.getBytes())).build().parseSignedClaims(token); + } catch (Exception e) { + claims = null; + } + return claims.getPayload(); + } + + private Date generateExpirationDate() { + long expiresIn = EXPIRES_IN; + return new Date(timeProvider.now().getTime() + expiresIn * 1000); + } + + public int getExpiredIn() { + return EXPIRES_IN; + } + + public Boolean validateToken(String token, UserDetails userDetails) { + User user = (User) userDetails; + final String username = getUsernameFromToken(token); + final Date created = getIssuedAtDateFromToken(token); + return (username != null && username.equals(userDetails.getUsername()) + && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())); + } + + private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { + return (lastPasswordReset != null && created.before(lastPasswordReset)); + } + + public String getToken(HttpServletRequest request) { + /** + * Getting the token from Authentication header e.g Bearer your_token + */ + String authHeader = getAuthHeaderFromHeader(request); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + return null; + } + + public String getAuthHeaderFromHeader(HttpServletRequest request) { + return request.getHeader(AUTH_HEADER); + } + } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 39ad835..522e0e0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,8 +1,8 @@ -app: - name: springboot-jwt-demo - -jwt: - header: Authorization - expires_in: 300 # 5 minutes - mobile_expires_in: 600 # 10 minutes - secret: p2H(++:$j*xKfKvK6n5k=},@@]CWkbj7d0iQ%6/;0}x6?[wNWR,$=0/HHx*&pJAq +app: + name: springboot-jwt-demo + +jwt: + header: Authorization + expires_in: 300 # 5 minutes + mobile_expires_in: 600 # 10 minutes + secret: ${JWT_SECRET:}