Changed from JWT token to public/privat key

This commit is contained in:
Andreas Dahm committed 2026-05-12 08:57:12 +02:00
1 parent 50366eb6bd
commit f8d39bcda6
6 files changed
+137 -19

No files matched your search

@@ -11,6 +11,6 @@ import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "security.jwt")
public class JwtConfig {
private String secret;
/** Base64-encoded X.509 DER Ed25519 public key. Verify-only; private key lives in Auth. */
private String publicKey;
}
@@ -1,24 +1,37 @@
package core_service.configuration.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* Verifies JWTs issued by tbp-auth-service. Verify-only: holds the Ed25519 public key.
*/
@Service
public class JwtService {
private static final String ED25519 = "Ed25519";
private final JwtConfig jwtConfig;
private PublicKey publicKey;
public JwtService(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
@PostConstruct
void init() {
this.publicKey = decodePublicKey(jwtConfig.getPublicKey());
}
public String extractEmail(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload()
@@ -27,7 +40,7 @@ public class JwtService {
public String extractUserId(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload()
@@ -36,7 +49,7 @@ public class JwtService {
public Long extractSteamId(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload()
@@ -46,7 +59,7 @@ public class JwtService {
public boolean isTokenValid(String token) {
try {
Jwts.parser()
.verifyWith(getSigningKey())
.verifyWith(publicKey)
.build()
.parseSignedClaims(token);
return true;
@@ -55,10 +68,12 @@ public class JwtService {
}
}
private SecretKey getSigningKey() {
String secretKey = jwtConfig.getSecret();
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
private static PublicKey decodePublicKey(String base64X509Der) {
try {
byte[] keyBytes = Base64.getDecoder().decode(base64X509Der);
return KeyFactory.getInstance(ED25519).generatePublic(new X509EncodedKeySpec(keyBytes));
} catch (Exception e) {
throw new IllegalStateException("Failed to decode Ed25519 public key", e);
}
}
}
+2 -1
View File
@@ -16,4 +16,5 @@ spring:
security:
jwt:
secret: "NzC9tQP1eTp496SxoVYslH1bqn81Tbfz74NjkFqdcVe7wayjcRN8IeZvJWd1s6NEWgwEXe9rdccvOn2HtJ4fdq"
# Dev-only Ed25519 public key. Must match Auth's dev private key.
public-key: "MCowBQYDK2VwAyEAPSmUISg0IUO1LrRPhF8RZAT/Yjh9HEVf5hzR84AL1Zw="
+1 -1
View File
@@ -36,7 +36,7 @@ management:
enabled: true
security:
jwt:
secret: ${JWT_SECRET}
public-key: ${JWT_PUBLIC_KEY}
app:
cors:
allowed-origins: "https://tbp.andreas-dahm.eu"
@@ -0,0 +1,102 @@
package core_service.configuration.security;
import core_service.shared.test.AbstractUnitTest;
import io.jsonwebtoken.Jwts;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.lenient;
/**
* Tests Core's verify-only JwtService against tokens signed with a matching Ed25519 private key.
*/
class JwtServiceTest extends AbstractUnitTest {
@Mock
private JwtConfig jwtConfig;
private JwtService jwtService;
private PrivateKey signingKey;
@BeforeEach
void setUp() throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
signingKey = keyPair.getPrivate();
String publicB64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
lenient().when(jwtConfig.getPublicKey()).thenReturn(publicB64);
jwtService = new JwtService(jwtConfig);
jwtService.init();
}
private String signToken(String userId, String email, Long steamId) {
var builder = Jwts.builder()
.subject(email)
.claim("userId", userId)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 60_000));
if (steamId != null) {
builder.claim("steamId", steamId);
}
return builder.signWith(signingKey, Jwts.SIG.EdDSA).compact();
}
@Test
void extractEmail_Success() {
var email = "test@example.com";
var token = signToken("0815", email, null);
assertEquals(email, jwtService.extractEmail(token));
}
@Test
void extractUserId_Success() {
var userId = "0815";
var token = signToken(userId, "test@example.com", null);
assertEquals(userId, jwtService.extractUserId(token));
}
@Test
void extractSteamId_Success() {
var steamId = 123456789L;
var token = signToken("0815", "test@example.com", steamId);
assertEquals(steamId, jwtService.extractSteamId(token));
}
@Test
void isTokenValid_Success() {
var token = signToken("0815", "test@example.com", null);
assertTrue(jwtService.isTokenValid(token));
}
@Test
void isTokenValid_Failure_garbage() {
assertFalse(jwtService.isTokenValid("not-a-jwt"));
}
@Test
void isTokenValid_Failure_wrongKey() throws Exception {
KeyPair other = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
String token = Jwts.builder()
.subject("test@example.com")
.expiration(new Date(System.currentTimeMillis() + 60_000))
.signWith(other.getPrivate(), Jwts.SIG.EdDSA)
.compact();
assertFalse(jwtService.isTokenValid(token));
}
}