Careeroza
JWT Setup in Spring Boot
Dependencies
We use jjwt (Java JWT) 0.12 to create and parse tokens. (Spring's own spring-boot-starter-oauth2-resource-server can also validate JWTs; the advanced level uses it with an authorization server.)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>Configuration
Example
Generate a secret once and pass it as an environment variable:
openssl rand -base64 32
export JWT_SECRET='q3Jx9mZt0Vb7Lw2pR8sYc4Kd1Hf6Ng5Ue0Ai3Tj7Ws='Example output
q3Jx9mZt0Vb7Lw2pR8sYc4Kd1Hf6Ng5Ue0Ai3Tj7Ws=
Config
application.yml
shopease:
jwt:
secret: ${JWT_SECRET}
access-ttl: 15m
issuer: https://api.shopease.inJwtService
package in.shopease.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@Service
public class JwtService {
private final SecretKey key;
private final Duration accessTtl;
private final String issuer;
public JwtService(@Value("${shopease.jwt.secret}") String secret,
@Value("${shopease.jwt.access-ttl}") Duration accessTtl,
@Value("${shopease.jwt.issuer}") String issuer) {
this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); // HS256, needs >= 32 bytes
this.accessTtl = accessTtl;
this.issuer = issuer;
}
public String generateAccessToken(UserDetails user) {
Instant now = Instant.now();
return Jwts.builder()
.issuer(issuer)
.subject(user.getUsername())
.claim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(accessTtl)))
.signWith(key)
.compact();
}
/** Verifies signature, expiry and issuer. Throws JwtException (e.g. ExpiredJwtException) if invalid. */
public Claims parse(String token) {
return Jwts.parser()
.verifyWith(key)
.requireIssuer(issuer)
.build()
.parseSignedClaims(token)
.getPayload();
}
public long accessTtlSeconds() {
return accessTtl.toSeconds();
}
}Tip: jjwt 0.12 renamed several methods: setSubject → subject, parserBuilder() → parser(), setSigningKey → verifyWith, parseClaimsJws → parseSignedClaims, getBody → getPayload. Old tutorials use the old names.