JwtService.java
package com.wilzwert.myjobs.infrastructure.security.service;
import com.wilzwert.myjobs.infrastructure.security.configuration.JwtProperties;
import com.wilzwert.myjobs.infrastructure.security.model.JwtToken;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
/**
* Provides JWT token generation and validation
* @author Wilhelm Zwertvaegher
*/
@Service
@Slf4j
public class JwtService {
private final JwtProperties jwtProperties;
public JwtService(final JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}
public Optional<String> getToken(HttpServletRequest request) {
String token;
Cookie[] cookies = request.getCookies();
if(cookies != null) {
Cookie jwtCookie = Arrays.stream(cookies)
.filter(cookie -> "access_token".equals(cookie.getName()))
.findFirst()
.orElse(null);
token = jwtCookie != null ? jwtCookie.getValue() : null;
if(token != null && !token.isEmpty()) {
return Optional.of(token);
}
}
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
token = request.getParameter("token");
if(token == null || token.isEmpty()) {
// log.info("Authorization header not found or not compatible with Bearer token");
return Optional.empty();
}
}
else {
token = authHeader.substring(7);
}
return Optional.of(token);
}
/**
* Extracts a JWT token from the current Request
* @param request the current request
* @return the extracted jwt token
* @throws ExpiredJwtException when JWT token is expired
* @throws MalformedJwtException when JWT token is malformed
* @throws IllegalArgumentException when JWT token is illegal
* @throws UnsupportedJwtException when JWT token is unsupported
* @throws SignatureException when JWT token's signature is invalid
*/
public Optional<JwtToken> extractTokenFromRequest(HttpServletRequest request) throws ExpiredJwtException, MalformedJwtException, IllegalArgumentException, UnsupportedJwtException, SignatureException {
return getToken(request).flatMap(this::parseToken);
}
public Optional<JwtToken> parseToken(String token) {
try {
Jwt<?, ?> parsedToken = Jwts
.parser().verifyWith(getSignInKey()).build().parse(token);
Claims claims = (Claims) parsedToken.getPayload();
JwtToken jwtToken = new JwtToken(claims.getSubject(), claims);
return Optional.of(jwtToken);
}
// we only catch different JwtException types to log warning messages
// the exceptions are then thrown again to be handled by the authentication filter
catch (ExpiredJwtException e) {
log.warn("Expired JWT token: {}", e.getMessage());
throw e;
}
catch (MalformedJwtException e) {
log.warn("Malformed JWT token: {}", e.getMessage());
throw e;
}
catch (SignatureException e) {
log.warn("Invalid JWT signature: {}", e.getMessage());
throw e;
}
catch (UnsupportedJwtException e) {
log.warn("Unsupported JWT token: {}", e.getMessage());
throw e;
}
catch (IllegalArgumentException e) {
log.warn("Empty JWT claims: {}", e.getMessage());
throw e;
}
}
/**
* Generates a token with a subject
* @param subject the subject we want to generate the token for (email, username...)
* @return the JWT Token
*/
public String generateToken(String subject) {
return Jwts
.builder()
.subject(subject)
// .claim("authType", authType)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + jwtProperties.getExpirationTime()*1000))
.signWith(getSignInKey())
.compact();
}
/**
*
* @return the signin key
*/
private SecretKey getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getSecretKey());
return Keys.hmacShaKeyFor(keyBytes);
}
}