Keycloak 访问令牌的独立验证

Standalone verification of Keycloak access-token

我正在使用 Keycloak 处理登录并生成 JWT 令牌。我需要能够验证发送到我的 REST API 服务的访问令牌。最佳实践是使用 JWT secret 直接验证令牌,而不是将其发送到 Keycloak 服务器进行验证。有很多 Java 这样做的例子,但我需要能够使用 python 或 ruby 来验证这一点。

我尝试了以下 python 签名验证,但出现 ValueError: Could not unserialize key data. 错误 我还尝试在 https://jwt.io 调试器中输入 public 密钥,但也得到了一个签名无效。

#!/usr/bin/env python3

import jwt

# Public key from Keycloak realm -> Keys -> Public Key -> (view)
public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB"

# Keycloak JWT RS256 access-token
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0LVlJOUlVc2R6NGM0SHoycXczT0xXZ0I0eHc2eFd4T29XdktVT2FvV3FzIn0.eyJqdGkiOiJjYzZkMjM5OS04ZmU5LTQzMWItYjZjMS05NWQxMWUxN2FiZGQiLCJleHAiOjE0ODE1NTQ2NTksIm5iZiI6MCwiaWF0IjoxNDgxNTU0MzU5LCJpc3MiOiJodHRwczovL3BtaS1rZXljbG9hay5zYnZpbXByb3Zlci5jb20vYXV0aC9yZWFsbXMvT3BlbkJFTCIsImF1ZCI6ImJlbG1nciIsInN1YiI6ImU1ODc2OGQxLWU3ODktNGU3Yi04ZGVlLWJjMzYxNzFkZDNhZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJlbG1nciIsIm5vbmNlIjoiMzc2ZDdjZmUtZGZkNC00Yzg5LWJlZGEtZTlmOWNmYWNlMTNkIiwiYXV0aF90aW1lIjoxNDgxNTUzMTY0LCJzZXNzaW9uX3N0YXRlIjoiN2E2OTEzYzItZWJkZC00MTc2LWI4YTAtZDc2NzVhNDZkNmJjIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiOTMyNGMzMjAtMmE4Zi00ODBlLTg5MjItZGQxNmFmMDQxZDdmIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJXaWxsaWFtIEhheWVzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJXaWxsaWFtIiwiZmFtaWx5X25hbWUiOiJIYXllcyIsImVtYWlsIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSJ9.Q7s-qTcJyH69Ebof8pQI1kZzeT8olwQnRJ06uas5TP2isacxOheHnJ9ixEvqTrr-iefmYMwx41jM68NCs6l8IBNHqv7t5-ediizx4ianMiXr7oZ_1oAT9hkLyrpv9iF2IZBtzNJz0GQAnDYe1moLOLuzqwvcUaWgmzRY95xvzo4kbE8OkeZiMpD_cDmp3_vKOsdn3B6ybJ9TXtea55A29pQzsvAM_6lHeyxTCisipOtu_ubnUOamkYSpxLwWZXgI1w7iz-igt-n7xtlFhUpra239yn9uly9iuBtlgnc3TFDmZn-XRq_PODDJNJeaQXDRaDqnRQhXsoObxCaPqXDQ3A"

access_token_json = jwt.decode(access_token, public_key)
print(access_token_json)

为了验证访问令牌,我做了以下事情:

  • 我重置了到期时间范围,所以我不必担心超时是一个复杂的因素。

  • 我必须将 BEGIN/END header/footer 添加到公钥(其中有两个基于公钥编码的不同版本 - 例如 'BEGIN RSA PUBLIC KEY'不是 Keycloak 公钥编码的正确版本):

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB
-----END PUBLIC KEY-----
  • 最后 - 我必须将 audience='belmgr' 添加到 python 脚本的 jwt.decode
## Python script 
#!/usr/bin/env python3
    
import jwt
    
public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB
-----END PUBLIC KEY-----"""
    
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0LVlJOUlVc2R6NGM0SHoycXczT0xXZ0I0eHc2eFd4T29XdktVT2FvV3FzIn0.eyJqdGkiOiJiMGFhZDllMC1lYmU5LTQ4ZDQtYTcxNC1iMWMyNjg4NmM2MDciLCJleHAiOjE0ODE5ODE0ODQsIm5iZiI6MCwiaWF0IjoxNDgxODk1MDg0LCJpc3MiOiJodHRwczovL3BtaS1rZXljbG9hay5zYnZpbXByb3Zlci5jb20vYXV0aC9yZWFsbXMvT3BlbkJFTCIsImF1ZCI6ImJlbG1nciIsInN1YiI6ImU1ODc2OGQxLWU3ODktNGU3Yi04ZGVlLWJjMzYxNzFkZDNhZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJlbG1nciIsIm5vbmNlIjoiMzI3NjRhYWUtZmM1NC00MDlkLTgxM2EtNjhhNmM4YTNhYzI2IiwiYXV0aF90aW1lIjoxNDgxODk1MDgzLCJzZXNzaW9uX3N0YXRlIjoiMWQwNDMyMDQtNWNkYy00OTVjLWJlZWUtODIwZWJiMmRlNWUzIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiMmEzZWY1MzgtN2MxOS00YzE3LTlmZTctYjQ3ZGNjNmM0ODQyIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJXaWxsaWFtIEhheWVzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJXaWxsaWFtIiwiZmFtaWx5X25hbWUiOiJIYXllcyIsImVtYWlsIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSJ9.DfC1c6BVBZ8Bgwu6CYGBsWp4T0dqltwAQ84E1Q0LdjFxvtVeDOF8rBIdgkr7rMCHObZWsEotljSR4BZzCvfDNmdk_25sedvi-ZHXTP0-nSeHczIXBstZ8p257A6-fEiIcG5CRoClHMI317bVGjNkzAV7l8kuBhr0bfrDedxpvKo3EQah4MrOF7-JXQGPAWlLDV1E9zsrT99Vm_XL58M-ur8q7N-B-CmOBV2GGsMEosTDK_-U-mattEN6PMNiG004Ryg0iPDM4-kr1AQsPE_wHBYf81_-vrqs7ec--0ShJYdC8-eBbuf9xVixNQVPRl7mnktaKA19YXdzdCwcQa6crw"
    
access_token_json = jwt.decode(access_token, public_key, audience='belmgr')
print(access_token_json)

我想补充一下这方面的知识,以防其他人卡在这上面。

直接从 Keycloak 领域 -> Keys -> Public Key -> (view) 复制的 public_key 值不起作用。相反,我必须通过使用 URL: public 来获取 Keycloak 为领域公开的 public 密钥:

https://<< my keycloak url >>/auth/realms/<< my realm >>/

此 url returns json 包含领域 "my realm" 的 "public_key" 值。按照所选答案中建议的方式使用 public 键的 THIS 值。

但是,您还需要确保解码调用的观众值正确。对于我的使用,值 "belmgr"。我通过使用 jwt.io、解码 access_token 找到了我需要的受众,并且在有效负载数据中,我发现:

{ ..."aud": "account",... }

使用 "account" 作为观众工作。

# wrong audience - FAIL
access_token_json = jwt.decode(access_token, public_key, audience='belmgr')

# right audience - SUCCESS
access_token_json = jwt.decode(access_token, public_key, audience='account')