Java离线验证Keycloak生成的JWT访问令牌

huangapple go评论129阅读模式
英文:

Java offline validation of JWT access token from Keycloak

问题

以下是翻译好的部分:

从Keycloak获取已签名的JWT令牌,地址为:http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token

  1. {
  2. "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......",
  3. "expires_in": 300,
  4. "refresh_expires_in": 1800,
  5. "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzMWRjZTBjNS01MGU0LTQxZjMtODAxNC1kMTcyMjdk....",
  6. "token_type": "bearer",
  7. "not-before-policy": 0,
  8. "session_state": "bd1728eb-ceda-43cf-a6e6-d637ba0da5e3",
  9. "scope": "email profile"
  10. }

因此,访问令牌为:

  1. eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......

我从Keycloak查询:http://localhost:8080/auth/realms/Myrealm/

  1. {
  2. "realm": "MyRealm",
  3. "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......",
  4. "token-service": "http://localhost:8080/auth/realms/Myrealm/protocol/openid-connect",
  5. "account-service": "http://localhost:8080/auth/realms/Myrealm/account",
  6. "tokens-not-before": 0
  7. }

因此,公钥为:

  1. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......

现在我正在尝试进行离线验证,代码如下:

  1. import java.security.KeyFactory;
  2. import java.security.spec.X509EncodedKeySpec;
  3. import java.security.PublicKey;
  4. String keyFromKeycloak = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......";
  5. byte[] bytes = keyFromKeycloak.getBytes();
  6. KeyFactory factory = KeyFactory.getInstance("RSA");
  7. X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(bytes);
  8. PublicKey pk = factory.generatePublic(encodedKeySpec);

但是我遇到了无效密钥的异常:

  1. java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
  2. at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:239)
  3. at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:352)
  4. Caused by: java.security.InvalidKeyException: invalid key format
  5. at java.base/sun.security.x509.X509Key.decode(X509Key.java:386)
  6. at java.base/sun.security.x509.X509Key.decode(X509Key.java:401)
  7. at java.base/sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:122)
  8. at java.base/sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:330)
  9. at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:235)
  10. ... 3 more

我需要PublicKey对象以便验证访问令牌,代码如下:

  1. Algorithm algorithm = Algorithm.RSA256(
  2. (RSAPublicKey) pk,
  3. null);
  4. JWTVerifier verifier = JWT.require(algorithm).build();
  5. DecodedJWT jwt = verifier.verify(token);

出了什么问题?我看到很多示例中都使用了这段代码,所以我怀疑问题可能出在密钥本身上。

英文:

I get a signed JWT token from Keycloak http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token:

  1. {
  2. &quot;access_token&quot;: &quot;eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......&quot;,
  3. &quot;expires_in&quot;: 300,
  4. &quot;refresh_expires_in&quot;: 1800,
  5. &quot;refresh_token&quot;: &quot;eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzMWRjZTBjNS01MGU0LTQxZjMtODAxNC1kMTcyMjdk....&quot;,
  6. &quot;token_type&quot;: &quot;bearer&quot;,
  7. &quot;not-before-policy&quot;: 0,
  8. &quot;session_state&quot;: &quot;bd1728eb-ceda-43cf-a6e6-d637ba0da5e3&quot;,
  9. &quot;scope&quot;: &quot;email profile&quot;
  10. }

So the access token is:

  1. eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......

I query Keycloak for http://localhost:8080/auth/realms/Myrealm/:

  1. {
  2. &quot;realm&quot;: &quot;MyRealm&quot;,
  3. &quot;public_key&quot;: &quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......&quot;,
  4. &quot;token-service&quot;: &quot;http://localhost:8080/auth/realms/Myrealm/protocol/openid-connect&quot;,
  5. &quot;account-service&quot;: &quot;http://localhost:8080/auth/realms/Myrealm/account&quot;,
  6. &quot;tokens-not-before&quot;: 0
  7. }

So the public key is:

  1. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......

Now I'm trying to do an offline validation as follows:

  1. import java.security.KeyFactory;
  2. import java.security.spec.X509EncodedKeySpec;
  3. import java.security.PublicKey;
  4. String keyFromKeycloak = &quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......&quot;;
  5. byte[] bytes = keyFromKeycloak.getBytes();
  6. KeyFactory factory = KeyFactory.getInstance(&quot;RSA&quot;);
  7. X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(bytes);
  8. PublicKey pk = factory.generatePublic(encodedKeySpec);

But I get an exception for invalid key:

  1. java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
  2. at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:239)
  3. at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:352)
  4. Caused by: java.security.InvalidKeyException: invalid key format
  5. at java.base/sun.security.x509.X509Key.decode(X509Key.java:386)
  6. at java.base/sun.security.x509.X509Key.decode(X509Key.java:401)
  7. at java.base/sun.security.rsa.RSAPublicKeyImpl.&lt;init&gt;(RSAPublicKeyImpl.java:122)
  8. at java.base/sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:330)
  9. at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:235)
  10. ... 3 more

I need the PublicKey object in order to validate the access token as follows:

  1. Algorithm algorithm = Algorithm.RSA256(
  2. (RSAPublicKey) pk,
  3. null);
  4. JWTVerifier verifier = JWT.require(algorithm).build();
  5. DecodedJWT jwt = verifier.verify(token);

What is wrong? I see a lot the examples with this code so I suspect the problem is with the key itself.

答案1

得分: 4

你的keyFromKeycloak字符串是一个经过Base64编码的DER SubjectPublicKeyInfo。你应该先对其进行解码,然后将其传递给X509EncodedKeySpec构造函数:

  1. String keyFromKeycloak = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......";
  2. byte[] keyBytes = Base64.getDecoder().decode(keyFromKeycloak);
  3. X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(keyBytes);
英文:

Your keyFromKeycloak String is a Base64 encoded DER SubjectPublicKeyInfo. You should first decode it and then pass it to the X509EncodedKeySpec constructor :

  1. String keyFromKeycloak = &quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......&quot;;
  2. byte[] keyBytes = Base64.getDecoder().decode(keyFromKeycloak);
  3. X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(keyBytes);

huangapple
  • 本文由 发表于 2020年9月24日 17:06:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/64043144.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定