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

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

Java offline validation of JWT access token from Keycloak

问题

以下是翻译好的部分:

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

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzMWRjZTBjNS01MGU0LTQxZjMtODAxNC1kMTcyMjdk....",
    "token_type": "bearer",
    "not-before-policy": 0,
    "session_state": "bd1728eb-ceda-43cf-a6e6-d637ba0da5e3",
    "scope": "email profile"
}

因此,访问令牌为:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......

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

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

因此,公钥为:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......

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

import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import java.security.PublicKey;

String keyFromKeycloak = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......";
byte[] bytes = keyFromKeycloak.getBytes();
KeyFactory factory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(bytes);
PublicKey pk = factory.generatePublic(encodedKeySpec);

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

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

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

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

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

英文:

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

{
&quot;access_token&quot;: &quot;eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......&quot;,
&quot;expires_in&quot;: 300,
&quot;refresh_expires_in&quot;: 1800,
&quot;refresh_token&quot;: &quot;eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzMWRjZTBjNS01MGU0LTQxZjMtODAxNC1kMTcyMjdk....&quot;,
&quot;token_type&quot;: &quot;bearer&quot;,
&quot;not-before-policy&quot;: 0,
&quot;session_state&quot;: &quot;bd1728eb-ceda-43cf-a6e6-d637ba0da5e3&quot;,
&quot;scope&quot;: &quot;email profile&quot;
}

So the access token is:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqQThGdzdhRk1rTGhGc2......

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

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

So the public key is:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......

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

import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import java.security.PublicKey;

String keyFromKeycloak = &quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......&quot;;
byte[] bytes = keyFromKeycloak.getBytes();
KeyFactory factory = KeyFactory.getInstance(&quot;RSA&quot;);
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(bytes);
PublicKey pk = factory.generatePublic(encodedKeySpec);

But I get an exception for invalid key:

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

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

Algorithm algorithm = Algorithm.RSA256(
    (RSAPublicKey) pk,
    null);
JWTVerifier verifier = JWT.require(algorithm).build();
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构造函数:

String keyFromKeycloak = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......";
byte[] keyBytes = Base64.getDecoder().decode(keyFromKeycloak);
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 :

String keyFromKeycloak = &quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKQF8ewcJ/pDzhgzbTfFCoS1FLjZyO5z7CbmeWl.......&quot;;
byte[] keyBytes = Base64.getDecoder().decode(keyFromKeycloak);
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:

确定