1
1
package info .unterrainer .commons .httpserver .accessmanager ;
2
2
3
3
import java .io .IOException ;
4
- import java .io . UncheckedIOException ;
4
+ import java .math . BigInteger ;
5
5
import java .net .URI ;
6
- import java .net .URISyntaxException ;
7
6
import java .net .http .HttpClient ;
8
7
import java .net .http .HttpRequest ;
9
- import java .net .http .HttpResponse .BodyHandlers ;
8
+ import java .net .http .HttpResponse ;
9
+ import java .security .KeyFactory ;
10
10
import java .security .PublicKey ;
11
+ import java .security .spec .RSAPublicKeySpec ;
12
+ import java .util .Base64 ;
11
13
import java .util .HashSet ;
12
14
import java .util .Set ;
13
15
14
16
import org .eclipse .jetty .http .HttpHeader ;
15
17
import org .keycloak .TokenVerifier ;
16
18
import org .keycloak .common .VerificationException ;
17
19
import org .keycloak .representations .AccessToken ;
18
- import org .keycloak .representations .idm .PublishedRealmRepresentation ;
19
20
21
+ import com .fasterxml .jackson .databind .JsonNode ;
20
22
import com .fasterxml .jackson .databind .ObjectMapper ;
21
23
22
24
import info .unterrainer .commons .httpserver .HttpServer ;
23
25
import info .unterrainer .commons .httpserver .enums .Attribute ;
24
26
import info .unterrainer .commons .httpserver .exceptions .ForbiddenException ;
25
- import info .unterrainer .commons .httpserver .exceptions .GatewayTimeoutException ;
26
27
import info .unterrainer .commons .httpserver .exceptions .UnauthorizedException ;
27
28
import info .unterrainer .commons .httpserver .jsons .UserDataJson ;
28
29
import io .javalin .core .security .AccessManager ;
@@ -56,6 +57,35 @@ public void manage(final Handler handler, final Context ctx, final Set<Role> per
56
57
handler .handle (ctx );
57
58
}
58
59
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
+
59
89
private void initPublicKey () {
60
90
if (publicKey != null )
61
91
return ;
@@ -66,36 +96,12 @@ private void initPublicKey() {
66
96
if (!realm .startsWith ("/" ))
67
97
realm = "/" + realm ;
68
98
69
- authUrl = host + "realms" + realm ;
99
+ authUrl = host + "realms" + realm + "/protocol/openid-connect/certs" ;
70
100
try {
71
101
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 );
99
105
throw new IllegalStateException (e );
100
106
}
101
107
}
0 commit comments