11package info .unterrainer .commons .httpserver .accessmanager ;
22
33import java .io .IOException ;
4- import java .io . UncheckedIOException ;
4+ import java .math . BigInteger ;
55import java .net .URI ;
6- import java .net .URISyntaxException ;
76import java .net .http .HttpClient ;
87import java .net .http .HttpRequest ;
9- import java .net .http .HttpResponse .BodyHandlers ;
8+ import java .net .http .HttpResponse ;
9+ import java .security .KeyFactory ;
1010import java .security .PublicKey ;
11+ import java .security .spec .RSAPublicKeySpec ;
12+ import java .util .Base64 ;
1113import java .util .HashSet ;
1214import java .util .Set ;
1315
1416import org .eclipse .jetty .http .HttpHeader ;
1517import org .keycloak .TokenVerifier ;
1618import org .keycloak .common .VerificationException ;
1719import org .keycloak .representations .AccessToken ;
18- import org .keycloak .representations .idm .PublishedRealmRepresentation ;
1920
21+ import com .fasterxml .jackson .databind .JsonNode ;
2022import com .fasterxml .jackson .databind .ObjectMapper ;
2123
2224import info .unterrainer .commons .httpserver .HttpServer ;
2325import info .unterrainer .commons .httpserver .enums .Attribute ;
2426import info .unterrainer .commons .httpserver .exceptions .ForbiddenException ;
25- import info .unterrainer .commons .httpserver .exceptions .GatewayTimeoutException ;
2627import info .unterrainer .commons .httpserver .exceptions .UnauthorizedException ;
2728import info .unterrainer .commons .httpserver .jsons .UserDataJson ;
2829import io .javalin .core .security .AccessManager ;
@@ -56,6 +57,35 @@ public void manage(final Handler handler, final Context ctx, final Set<Role> per
5657 handler .handle (ctx );
5758 }
5859
60+ private PublicKey fetchPublicKey (String jwksUrl ) throws Exception {
61+ ObjectMapper objectMapper = new ObjectMapper ();
62+ HttpClient client = HttpClient .newHttpClient ();
63+ HttpRequest request = HttpRequest .newBuilder ().uri (URI .create (jwksUrl )).GET ().build ();
64+
65+ HttpResponse <String > response = client .send (request , HttpResponse .BodyHandlers .ofString ());
66+
67+ if (response .statusCode () >= 300 ) {
68+ throw new IOException ("Failed to fetch JWKS: HTTP " + response .statusCode ());
69+ }
70+
71+ JsonNode jwks = objectMapper .readTree (response .body ());
72+ // Just take the first key for now.
73+ JsonNode key = jwks .get ("keys" ).get (0 );
74+
75+ String modulusBase64 = key .get ("n" ).asText ();
76+ String exponentBase64 = key .get ("e" ).asText ();
77+
78+ byte [] modulusBytes = Base64 .getUrlDecoder ().decode (modulusBase64 );
79+ byte [] exponentBytes = Base64 .getUrlDecoder ().decode (exponentBase64 );
80+
81+ BigInteger modulus = new BigInteger (1 , modulusBytes );
82+ BigInteger exponent = new BigInteger (1 , exponentBytes );
83+
84+ RSAPublicKeySpec spec = new RSAPublicKeySpec (modulus , exponent );
85+ KeyFactory factory = KeyFactory .getInstance ("RSA" );
86+ return factory .generatePublic (spec );
87+ }
88+
5989 private void initPublicKey () {
6090 if (publicKey != null )
6191 return ;
@@ -66,36 +96,12 @@ private void initPublicKey() {
6696 if (!realm .startsWith ("/" ))
6797 realm = "/" + realm ;
6898
69- authUrl = host + "realms" + realm ;
99+ authUrl = host + "realms" + realm + "/protocol/openid-connect/certs" ;
70100 try {
71101 log .info ("Getting public key from: [{}]" , authUrl );
72- HttpClient client = HttpClient .newHttpClient ();
73- HttpRequest request = HttpRequest .newBuilder ().uri (new URI (authUrl )).build ();
74- ObjectMapper objectMapper = new ObjectMapper ();
75-
76- client .sendAsync (request , BodyHandlers .ofString ()).thenApply (response -> {
77- if (response .statusCode () >= 300 ) {
78- log .error ("HTTP status [{}] getting public key from keycloak instance [{}]." , response .statusCode (),
79- authUrl );
80- throw new GatewayTimeoutException (String
81- .format ("The keycloak instance returned an error (status: %d)." , response .statusCode ()));
82- }
83- return response .body ();
84- }).thenAccept (body -> {
85- if (body == null ) {
86- log .warn ("Received empty body." );
87- return ;
88- }
89- try {
90- publicKey = objectMapper .readValue (body , PublishedRealmRepresentation .class ).getPublicKey ();
91- log .info ("Public key received." );
92- } catch (IOException e ) {
93- log .error ("Error parsing answer from keycloak." );
94- throw new UncheckedIOException (e );
95- }
96- }).join ();
97- } catch (URISyntaxException e ) {
98- log .error ("The keycloak URL was illegal [{}]." , authUrl );
102+ publicKey = fetchPublicKey (authUrl );
103+ } catch (Exception e ) {
104+ log .error ("There was an error fetching the PublicKey from the openIdConnect-server [{}]." , authUrl );
99105 throw new IllegalStateException (e );
100106 }
101107 }
0 commit comments