24

I realized there are many iterations of this questions. But I can't seem to understand the answer correctly.

We have secured our rabbitmq and rest endpoints with a oauth2 spring server similar to this post. But it doesn't have all of the features we need and want. So we would like to use Keycloak. I have been successful with securing the rest endpoint by just going to the new version of spring security 5.1 and specifing the security.oauth2.resource.jwk.key-set-uri and setting the necessary dependencies and configuration.

While trying to secure the RabbitMQ, I have been running into problems checking the bearer token from the message header because the keycloak jwks endpoint isn't returning the true RSA public key.

RabbitMQ uses the CustomMessageListenerContainer to get the token from the message header and uses the DefaultTokenServices to check the token.

From my understanding, the endpoint that responds with the key is https://keycloak-server/auth/realms/my-realm/protocol/openid-connect/certs Doing a HttpGet on this endpoint, I get a response that looks like the following

{
    "keys": [{
            "kid": "7JUbcl_96GNk2zNh4MAORuEz3YBuprXilmTXjm0gmRE",
            "kty": "RSA",
            "alg": "RS256",
            "use": "sig",
            "n": "nE9gEtzZvV_XisnAY8Hung399hwBM_eykZ9J57euboEsKra8JvDmE6w7SSrk-aTVjdNpjdzOyrFd4V7tFqev1vVJu8MJGIyQlbPv07MTsgYE5EPM4DxdQ7H6_f3vQjq0hznkFvC-hyCqUhxPTXM5NgvH86OekL2C170xnd50RLWw8FbrprP2oRjgBnXMAif1Dd8kwbKKgf5m3Ou0yTVGfsCRG1_LSj6gIEFglxNHvGz0RejoQql0rGMxcW3MzCvc-inF3FCafQTrG5eWHqp5xXEeMHz0JosQ7BcT8MVp9lHT_utiazhQ1uKZEb4uoYOyy6mDDkx-wExpZkOx76bk_Yu-N25ljY18hNllnV_8gVMkX46_vcc-eN3DRZGNJ-Asd_sZrjbXbAvBbKwVxZeOTaXiUdvl8O0G5xX2xPnS_WA_1U4b_V1t28WtnX4bqGlOejW2kkjLvNrpfQ5fnvLjkl9I2B16Mbh9nS0LJD0RR-AkBsv3rKEnMyEkW9UsfgYKLFKuH32x_CXi9uyvNDas_q8WS3QvYwAGEMRO_4uICDAqupCVb1Jcs9dvd1w-tUfj5MQOXB-srnQYf5DbFENTNM1PK390dIjdLJh4k2efCJ21I1kYw2Qr9lHI4X2peTinViaoOykykJiol6LMujUcfqaZ1qPKDy_UnpAwGg9NyFU",
            "e": "AQAB"
        }
    ]
}

From my understanding, the field with key "n" is supposed to be an RSA256 key. Adding it to a RSAVerifier eventually gets an error of "Caused by: org.springframework.security.jwt.codec.InvalidBase64CharacterException: Bad Base64 input character decimal 95 in array position 2."

However, if I login to keycloak admin page and go into the realm settings-> keys and click the public key, a popup shows the public key minus the "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" headers and footers. Hard coding this enables everything to work.

Is the key encoded? I've tried doing a Base64Utils.decodeFromUrlSafeString and a Base64Utils.decodeFromString. The first returning something smaller and doesn't lool like the key and the later creating an Illegal argument exception Illegal base64 character 5f.

Update: The n being returned is the modulous and e is the public exponent of the public key. But how does one get the actual key string?

Thomas Lann
  • 1,124
  • 5
  • 17
  • 35

5 Answers5

44

The keys are also directly on https://keycloak-server/auth/realms/my-realm, in a format directly exploitable with your code:

{
  "realm": "my-realm",
  "public_key": "MIIBI...",
  "token-service": "https://keycloak-server/auth/realms/my-realm/protocol/openid-connect",
  "account-service": "https://keycloak-server/auth/realms/my-realm/account",
  "tokens-not-before": 0
}
Cédric Couralet
  • 4,913
  • 1
  • 21
  • 21
  • 3
    I really wish this would of been better documented. This would of saved me a lot of time. Thank you for your answer. – Thomas Lann Jan 23 '19 at 16:09
  • I also have to ask, how did you know this answer? I have spent quite a long time looking through the documentation and it only references the jwks_uri endpoint. – Thomas Lann Jan 23 '19 at 16:13
  • 2
    @"Thomas Lann" I stumbled upon it while testing, you're right it is not really advertised, which also means I don't really know if it will stay or not. – Cédric Couralet Jan 24 '19 at 08:54
  • I found in the Spring oauth2 code a partial example of converting the jwk to a RSAVerifier. So I have something that works, but it isn't very good code. I'm having a hard time finding good examples of using JWKs outside of regular spring boot by setting the uri in a property. I imagine it has something to do with creating a JwkTokenStore similar to a JwtTokenStore. – Thomas Lann Jan 24 '19 at 21:51
  • With the below endpoint, you can get list of keycloak openId endpoints exposed `http://localhost:8080/auth/realms/{my_realm}/.well-known/openid-configuration ` To get 'jwks_uri' use the below endpoint `http://localhost:8080/auth/realms/{my_realm}/protocol/openid-connect/certs` Just for anyone, who would stumble upone here.. – amj Dec 06 '19 at 14:21
  • 2
    Any idea how to get the expiration of the public key? I assume it should eventually rotate. I inspected the certificate from keycloak's UI where you can manually get the public key, and it was a 10 year expiration. But that's not a given that it isn't scheduled to change before that. I'm expecting there to be a short outage, unexplained, some years down the road. :-/ For now I'm just caching the key for 15m at a time. – Davy Durham Jan 01 '20 at 05:14
  • 2
    Do You know how a Java client to call this endpoint? I'm using `keycloak-admin-client` and when I call `keycloak.realm(realm).toRepresentation().getPublicKey()` it gives null value. I checked and it calls `/admin/realms/realmname`, not `/auth/realms/realmname`. – zolv Mar 19 '21 at 17:59
  • 2
    OK, I've found it: `keycloak.realm(realm).keys().getKeyMetadata().getKeys()` – zolv Mar 19 '21 at 22:25
  • 1
    For those coming here in 2023, the url changed in v17 https://keycloak-server/realms/my-realm and removed the "auth" part... See https://github.com/keycloak/keycloak/issues/10464 – AdamE May 16 '23 at 22:43
12

I'll found it also on:

  • open admin console
  • choose realm
  • choose Realm Settings
  • open tab 'Keys'
  • open tab 'active'
  • in the column 'Public keys' press 'Public Key enter image description here
  • a popup with the public key appears.
robrecht
  • 177
  • 1
  • 4
3

There is toIntegerBytes before base64 encode, so it is not just base64 decode. Try:

BigInteger modulus = new BigInteger(1, Base64.decodeBase64("n-value-here"));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64("e-value-here"));
Jan Garaj
  • 25,598
  • 3
  • 38
  • 59
2

JWKS endpoints are designed to have their keys changed over time (in a process called key rotation), so retrieving the public key as per the accepted answer is not a good idea. What you should opt for instead is to use a JWKS client. I use node-jwks-rsa, but the same creators also have a java implementation (jwks-rsa-java).

bruceoutdoors
  • 420
  • 5
  • 8
2

open up : replace YOUR_PORT and YOUR_REALM

http://localhost:YOUR_PORT/auth/realms/YOUR_REALM/.well-known/openid-configuration

open first link

issuer

enter image description here

your public key is here:

enter image description here

saber tabatabaee yazdi
  • 4,404
  • 3
  • 42
  • 58