Public Access
feat(login): publish verify key as JWKS
Auth serves its Ed25519 public key at GET /.well-known/jwks.json, so Core can find the key by the token kid. Startup fails on a blank JWT_KID, because compose turns an unset value into "" and Core finds keys only by kid.
This commit is contained in:
1 parent
3a48ce0ff1
commit
600b0b143f
7 files changed
+110
-3
No files matched your search
@@ -12,13 +12,18 @@ import java.security.KeyPairGenerator;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for JwtService (EdDSA / Ed25519).
|
||||
@@ -124,4 +129,36 @@ class JwtServiceTest extends AbstractUnitTest {
|
||||
void isTokenValid_ProfileTicketIsNoLoginToken() {
|
||||
assertFalse(jwtService.isTokenValid(jwtService.generateProfileTicket(42L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void init_BlankKid_FailsAtStartup() {
|
||||
when(jwtConfig.getKid()).thenReturn(" ");
|
||||
JwtService service = new JwtService(jwtConfig);
|
||||
|
||||
assertThrows(IllegalStateException.class, service::init);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicJwks_PublishesTheVerifyKeyAsOkpJwk() throws Exception {
|
||||
List<Map<String, ?>> jwks = jwtService.publicJwks();
|
||||
|
||||
assertEquals(1, jwks.size());
|
||||
Map<String, ?> jwk = jwks.getFirst();
|
||||
assertEquals("OKP", jwk.get("kty"));
|
||||
assertEquals("Ed25519", jwk.get("crv"));
|
||||
assertEquals("EdDSA", jwk.get("alg"));
|
||||
assertEquals("sig", jwk.get("use"));
|
||||
assertEquals(KID, jwk.get("kid"));
|
||||
byte[] raw = Base64.getUrlDecoder().decode((String) jwk.get("x"));
|
||||
assertEquals(32, raw.length);
|
||||
|
||||
// A verifier that builds the key from "x" alone accepts a token of this service.
|
||||
byte[] prefix = HexFormat.of().parseHex("302a300506032b6570032100");
|
||||
byte[] der = new byte[prefix.length + raw.length];
|
||||
System.arraycopy(prefix, 0, der, 0, prefix.length);
|
||||
System.arraycopy(raw, 0, der, prefix.length, raw.length);
|
||||
PublicKey fromJwk = KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(der));
|
||||
String token = jwtService.generateToken("7", "a@b.de", null);
|
||||
assertEquals("7", Jwts.parser().verifyWith(fromJwk).build().parseSignedClaims(token).getPayload().get("userId"));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import java.util.Set;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@AutoConfigureMockMvc
|
||||
@@ -27,6 +28,15 @@ class PublicEndpointsTest extends AbstractIntegrationTest {
|
||||
.andExpect(jsonPath("$.components").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void jwksIsPublicAndCacheable() throws Exception {
|
||||
mockMvc.perform(get("/.well-known/jwks.json"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("Cache-Control", org.hamcrest.Matchers.containsString("max-age=300")))
|
||||
.andExpect(jsonPath("$.keys[0].kty").value("OKP"))
|
||||
.andExpect(jsonPath("$.keys[0].kid").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void prometheusIsNotPublic() throws Exception {
|
||||
int status = mockMvc.perform(get("/actuator/prometheus")).andReturn().getResponse().getStatus();
|
||||
|
||||
Reference in new issue
Block a user