KeyCloak jti声明存储在哪里?
KeyCloak Where the jti claim is stored?
我正在尝试研究 keycloak 提供的 Preventing Replay Attack,我认为它使用 jti 声明来处理它
首先我通过 openid RESTAPI (...protocol/openid-connect/token) 登录它 return JWT 这样的
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlX0l1RWo2cWEza2ZMb1MyVUwyNGJUMGJKUElXRWRkU3YxM2RSd1ZTM1lzIn0.eyJqdGkiOiI2ZjE3OGQxMi00Mzc3LTQ5MzEtOTljOC1lYmIyNDk1OWY3NmIiLCJleHAiOjE1NjY5NzczOTMsIm5iZiI6MCwiaWF0IjoxNTY2OTc3MzMzLCJpc3MiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibWFzdGVyLXJlYWxtIiwiYWNjb3VudCJdLCJzdWIiOiI0OTEwMGFiZC00ZGFjLTQ5MzQtOTUwYi05N2I0ZGMxYmI5MGMiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWN1cml0eS1hZG1pbi1jb25zb2xlIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiNmJkZTM5ZGYtN2FkZi00MDVlLThjYTEtMGI3NDlhYWUwN2Q1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InN1cGVyYWRtaW4gc3VwZXJhZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6InN1cGVyYWRtaW4iLCJnaXZlbl9uYW1lIjoic3VwZXJhZG1pbiIsImZhbWlseV9uYW1lIjoic3VwZXJhZG1pbiIsInVzZXIiOnsiZGV2aWNlSWQiOlsiMTIzNCJdfSwiZW1haWwiOiJzdXBlcmFkbWluQGdtYWlsLmNvbSJ9.OZnw3SbaBpVJSN1KbHMcdmP-zt55AIxmBv3ddyvfXEV-zqStH_TkmZ6P36oDoKu-UctGb9KdemmO0EHM0z1tN4vk35WtS5K3luWtYv42FWvx67mifUxc9BCsgXPz4qx78Kd05UzQ6297NqAAiDfU8gdeywT3mNZ_2AoT45Sw5Sb1cCq8pAJokOHT2PSLHGgTYpY6wbSKe9msfchmzJv1FZK1RnLuLY9HwDhbn_VDIgWlmro8bXNq5eTLAVtnzEL2vEokeFdKDlnPfoBk1oPE5XfjVaqoSBo5yxwxPMKDX_g4EayOXHjQqRCTTKdZm3Ah14DN0t8XBWi3p2vdUhqoIA",
"expires_in": 59,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3ZTVmNzhhYy05ODVmLTRjMTgtYmMwYS1kMDJjZDFlOGRhNGQifQ.eyJqdGkiOiJmNTEwMjUxNC0wMGE4LTQzNDEtOTljOC1mNjg3ZjBmOTk0MTMiLCJleHAiOjE1NjY5NzkxMzQsIm5iZiI6MCwiaWF0IjoxNTY2OTc3MzM0LCJpc3MiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI0OTEwMGFiZC00ZGFjLTQ5MzQtOTUwYi05N2I0ZGMxYmI5MGMiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoic2VjdXJpdHktYWRtaW4tY29uc29sZSIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjZiZGUzOWRmLTdhZGYtNDA1ZS04Y2ExLTBiNzQ5YWFlMDdkNSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCJ9.Hsaib16poW3SW0EYUB80jU0HyseZi_Ui9tj_2QJAZ-w",
"token_type": "bearer",
"not-before-policy": 1566975958,
"session_state": "6bde39df-7adf-405e-8ca1-0b749aae07d5",
"scope": "profile email"
}
access_token 解码你会看到 jti claim
{
"jti": "6f178d12-4377-4931-99c8-ebb24959f76b",
"exp": 1566977393,
"nbf": 0,
"iat": 1566977333,
"iss": "http://192.168.99.100:8080/auth/realms/master",
"aud": [
"master-realm",
"account"
],
...
接下来我会在管理员页面看到新的会话列表,如下图
其次,我使用 access_token 获取用户信息 RESTAPI (.../protocol/openid-connect/userinfo) 它 return 这样的响应
{
"sub": "49100abd-4dac-4934-950b-97b4dc1bb90c",
"email_verified": false,
"name": "superadmin superadmin",
"preferred_username": "superadmin",
"given_name": "superadmin",
"family_name": "superadmin",
"user": {
"deviceId": []
},
"email": "superadmin@gmail.com"
}
当我在后台点击注销所有会话时,存储中的 jti 被删除,我再次获得用户信息return
{
"error": "invalid_request",
"error_description": "User session not found or doesn't have client attached on it"
}
jti 声明存储在哪里?
JTI 声明值只是一个无处存储的随机 UUID。您正在寻找的是 session_state
参数,它是会话 ID。当您将 access_token
传递给 /userinfo
端点时,Keycloak 检索 session_state
值并在分布式缓存(Infinispan)中搜索相应的会话。
单击 logout all sessions
按钮后,Keycloak 会清除缓存中的所有会话。
更新
https://www.keycloak.org/docs/latest/server_admin/index.html#compromised-access-and-refresh-tokens
Another thing you can do to mitigate leaked access tokens is to shorten their lifespans. You can specify this within the timeouts page. Short lifespans (minutes) for access tokens for clients and applications to refresh their access tokens after a short amount of time. If an admin detects a leak, they can logout all user sessions to invalidate these refresh tokens or set up a revocation policy. Making sure refresh tokens always stay private to the client and are never transmitted ever is very important as well.
上面的 link 中有更多关于安全注意事项的信息。
令牌黑名单
Keycloak中没有Token Blacklist之类的东西,也不应该有。代币是代表特定客户的用户或服务帐户发行的。因此,如果您想让普通用户访问某些受 Keycloak 保护的服务,您只需在 Keycloak 管理控制台中创建该用户即可。对于机器对机器通信中的第三方应用程序,您创建一个启用了服务帐户的客户端,第三方应用程序使用其 client_id 和秘密来代表自己颁发访问和刷新令牌。
您只需注销(从缓存中删除)用户或客户端的会话,而不是将令牌列入黑名单。
如果您想完全删除合作伙伴对 API 的访问权限,您可以只禁用客户端(管理员 -> 客户端 -> 客户端 -> 将启用设置为 false)或用户(管理员 -> 用户 -> 用户-> 将启用设置为 false)。
我正在尝试研究 keycloak 提供的 Preventing Replay Attack,我认为它使用 jti 声明来处理它
首先我通过 openid RESTAPI (...protocol/openid-connect/token) 登录它 return JWT 这样的
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlX0l1RWo2cWEza2ZMb1MyVUwyNGJUMGJKUElXRWRkU3YxM2RSd1ZTM1lzIn0.eyJqdGkiOiI2ZjE3OGQxMi00Mzc3LTQ5MzEtOTljOC1lYmIyNDk1OWY3NmIiLCJleHAiOjE1NjY5NzczOTMsIm5iZiI6MCwiaWF0IjoxNTY2OTc3MzMzLCJpc3MiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibWFzdGVyLXJlYWxtIiwiYWNjb3VudCJdLCJzdWIiOiI0OTEwMGFiZC00ZGFjLTQ5MzQtOTUwYi05N2I0ZGMxYmI5MGMiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWN1cml0eS1hZG1pbi1jb25zb2xlIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiNmJkZTM5ZGYtN2FkZi00MDVlLThjYTEtMGI3NDlhYWUwN2Q1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InN1cGVyYWRtaW4gc3VwZXJhZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6InN1cGVyYWRtaW4iLCJnaXZlbl9uYW1lIjoic3VwZXJhZG1pbiIsImZhbWlseV9uYW1lIjoic3VwZXJhZG1pbiIsInVzZXIiOnsiZGV2aWNlSWQiOlsiMTIzNCJdfSwiZW1haWwiOiJzdXBlcmFkbWluQGdtYWlsLmNvbSJ9.OZnw3SbaBpVJSN1KbHMcdmP-zt55AIxmBv3ddyvfXEV-zqStH_TkmZ6P36oDoKu-UctGb9KdemmO0EHM0z1tN4vk35WtS5K3luWtYv42FWvx67mifUxc9BCsgXPz4qx78Kd05UzQ6297NqAAiDfU8gdeywT3mNZ_2AoT45Sw5Sb1cCq8pAJokOHT2PSLHGgTYpY6wbSKe9msfchmzJv1FZK1RnLuLY9HwDhbn_VDIgWlmro8bXNq5eTLAVtnzEL2vEokeFdKDlnPfoBk1oPE5XfjVaqoSBo5yxwxPMKDX_g4EayOXHjQqRCTTKdZm3Ah14DN0t8XBWi3p2vdUhqoIA",
"expires_in": 59,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3ZTVmNzhhYy05ODVmLTRjMTgtYmMwYS1kMDJjZDFlOGRhNGQifQ.eyJqdGkiOiJmNTEwMjUxNC0wMGE4LTQzNDEtOTljOC1mNjg3ZjBmOTk0MTMiLCJleHAiOjE1NjY5NzkxMzQsIm5iZiI6MCwiaWF0IjoxNTY2OTc3MzM0LCJpc3MiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vMTkyLjE2OC45OS4xMDA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI0OTEwMGFiZC00ZGFjLTQ5MzQtOTUwYi05N2I0ZGMxYmI5MGMiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoic2VjdXJpdHktYWRtaW4tY29uc29sZSIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjZiZGUzOWRmLTdhZGYtNDA1ZS04Y2ExLTBiNzQ5YWFlMDdkNSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCJ9.Hsaib16poW3SW0EYUB80jU0HyseZi_Ui9tj_2QJAZ-w",
"token_type": "bearer",
"not-before-policy": 1566975958,
"session_state": "6bde39df-7adf-405e-8ca1-0b749aae07d5",
"scope": "profile email"
}
access_token 解码你会看到 jti claim
{
"jti": "6f178d12-4377-4931-99c8-ebb24959f76b",
"exp": 1566977393,
"nbf": 0,
"iat": 1566977333,
"iss": "http://192.168.99.100:8080/auth/realms/master",
"aud": [
"master-realm",
"account"
],
...
接下来我会在管理员页面看到新的会话列表,如下图
其次,我使用 access_token 获取用户信息 RESTAPI (.../protocol/openid-connect/userinfo) 它 return 这样的响应
{
"sub": "49100abd-4dac-4934-950b-97b4dc1bb90c",
"email_verified": false,
"name": "superadmin superadmin",
"preferred_username": "superadmin",
"given_name": "superadmin",
"family_name": "superadmin",
"user": {
"deviceId": []
},
"email": "superadmin@gmail.com"
}
当我在后台点击注销所有会话时,存储中的 jti 被删除,我再次获得用户信息return
{
"error": "invalid_request",
"error_description": "User session not found or doesn't have client attached on it"
}
jti 声明存储在哪里?
JTI 声明值只是一个无处存储的随机 UUID。您正在寻找的是 session_state
参数,它是会话 ID。当您将 access_token
传递给 /userinfo
端点时,Keycloak 检索 session_state
值并在分布式缓存(Infinispan)中搜索相应的会话。
单击 logout all sessions
按钮后,Keycloak 会清除缓存中的所有会话。
更新
https://www.keycloak.org/docs/latest/server_admin/index.html#compromised-access-and-refresh-tokens
Another thing you can do to mitigate leaked access tokens is to shorten their lifespans. You can specify this within the timeouts page. Short lifespans (minutes) for access tokens for clients and applications to refresh their access tokens after a short amount of time. If an admin detects a leak, they can logout all user sessions to invalidate these refresh tokens or set up a revocation policy. Making sure refresh tokens always stay private to the client and are never transmitted ever is very important as well.
上面的 link 中有更多关于安全注意事项的信息。
令牌黑名单
Keycloak中没有Token Blacklist之类的东西,也不应该有。代币是代表特定客户的用户或服务帐户发行的。因此,如果您想让普通用户访问某些受 Keycloak 保护的服务,您只需在 Keycloak 管理控制台中创建该用户即可。对于机器对机器通信中的第三方应用程序,您创建一个启用了服务帐户的客户端,第三方应用程序使用其 client_id 和秘密来代表自己颁发访问和刷新令牌。
您只需注销(从缓存中删除)用户或客户端的会话,而不是将令牌列入黑名单。
如果您想完全删除合作伙伴对 API 的访问权限,您可以只禁用客户端(管理员 -> 客户端 -> 客户端 -> 将启用设置为 false)或用户(管理员 -> 用户 -> 用户-> 将启用设置为 false)。