← Back to Documentation Portal|📊 Coverage Quality Gates|Coverage Dashboard
Payflow API — Automated JaCoCo Code Coverage (90% Line / 73% Branch)

JwtTokenProvider.java

package com.payflow.security;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.crypto.SecretKey;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

@Component
public final class JwtTokenProvider {

	private static final Logger LOG = LoggerFactory.getLogger(JwtTokenProvider.class);
	public static final String DEFAULT_SECRET = "404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970";

	private final SecretKey secretKey;
	private final long expirationMs;

	@Autowired
	public JwtTokenProvider( //
			@Value("${payflow.security.jwt.secret:" + DEFAULT_SECRET + "}") String secret,
			@Value("${payflow.security.jwt.expiration-ms:3600000}") long expirationMs, //
			Environment environment) {
		boolean isProd = environment != null
				&& (environment.matchesProfiles("prod") || environment.matchesProfiles("prod-light"));
		if (isProd) {
			boolean isWeakSecret = secret == null || secret.isBlank() || DEFAULT_SECRET.equals(secret)
					|| secret.length() < 32;
			if (isWeakSecret) {
				String msg = "Production environment requires external PAYFLOW_SECURITY_JWT_SECRET "
						+ "of >= 256 bits. Default secret is prohibited.";
				throw new IllegalStateException(msg);
			}
		}
		this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
		this.expirationMs = expirationMs;
	}

	public JwtTokenProvider(String secret, long expirationMs) {
		this(secret, expirationMs, null);
	}

	public String generateToken(String upiId, UUID referenceId) {
		Date now = Date.from(Instant.now());
		Date expiry = Date.from(Instant.now().plusMillis(expirationMs));

		var builder = Jwts.builder();
		builder.subject(upiId);
		builder.claim("referenceId", referenceId.toString());
		builder.claim("roles", List.of("ROLE_USER"));
		builder.issuedAt(now);
		builder.expiration(expiry);
		builder.signWith(secretKey);
		return builder.compact();
	}

	public boolean validateToken(String token) {
		try {
			Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token);
			return true;
		} catch (JwtException | IllegalArgumentException ex) {
			LOG.debug("Invalid JWT token: {}", ex.getMessage());
			return false;
		}
	}

	public String getUpiIdFromToken(String token) {
		Claims claims = getClaims(token);
		return claims.getSubject();
	}

	public UUID getReferenceIdFromToken(String token) {
		Claims claims = getClaims(token);
		String refIdStr = claims.get("referenceId", String.class);
		return UUID.fromString(refIdStr);
	}

	public long getExpirationSeconds() {
		return expirationMs / 1000;
	}

	private Claims getClaims(String token) {
		return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload();
	}
}