Keycloak-Oauth-2身份验证流程

时间:2019-05-17 13:00:41

标签: rest authentication oauth-2.0 keycloak

我们正尝试在我的一些REST应用程序的通信中集成(我和我的同事)Oauth2身份验证。作为身份验证提供者和管理者,我们想使用Keycloak。我们阅读了一些文档,并且认为我们了解了它的工作原理。现在,我们想知道的是,我们设计的流认证是否遵守Oauth2协议的原理。 我们决定使用的流程auth涉及令牌的生成,其验证为:

graphic representation of auth-flow

让我们继续描述两个使用Oauth2进行身份验证的应用程序(应用程序A和应用程序B)之间的数据交换示例。上图显示了A应用程序如何查询Keycloak以请求令牌。随后,应用程序A通过输入从Keycloak收到的令牌将请求发送到应用程序B;此时,应用程序B在收到令牌后,查询Keycloak并要求其验证收到的令牌;最后,应用程序B将收到有关令牌有效性的答案,并基于此答复,可以决定是接受还是拒绝从应用程序A收到的请求。

让我们看看交换的技术细节:

1-应用程序A,通过身份验证要求Keycloak生成新令牌。

HTTP请求的示例:

POST /auth/realms/OMS/protocol/openid-connect/token HTTP/1.1
Host: local-keycloak.it:8080
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache

client_id=oms-test&client_secret=039a6b94-44a7-4dae-b8a4-e7b673eee8e5&grant_type=client_credentials&scope=openid
  • / auth / realms / {REALM_NAME} / protocol / openid-connect / token:密钥斗篷 端点以调用以生成和获取令牌。

发送到服务器的HTTP消息的主体本质上是一个巨大的查询字符串-名称/值对由&符分隔,名称与值由等号(=)分隔。实际上,正文的内容类型表示为“ application / x-www-form-urlencoded”。

2-密钥泄漏,如果应用程序A身份验证成功,则生成令牌并将其转发给它。

HTTP响应示例:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2NzhjY2M0NC03ZTY3LTRmODAtOTk5NC1hOTA0NmI3NGY2YTgifQ.eyJqdGkiOiJiYTU3NzZjYi03Zjg1LTRhNTAtOGM5Ni1kYWQ3OTRlZGRjZWIiLCJleHAiOjE1NTc5OTY4NTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL09NUyIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvbXMtdGVzdCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImVhNWUxYzQyLWI5MzUtNGRhMC05ZDY2LWEwMjlmZGY3NzdiMiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSJ9.BkXWMLmuf1c0OBUeg2P2262LLvTmhXg46y4-rrvebNE",
    "token_type": "bearer",
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiIyYjdjZGI2Ny1kYjM3LTQ5MTQtYWNiYi0xNmU5MDA4YzQ4N2IiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoib21zLXRlc3QiLCJzdWIiOiI2MGU4YjY1Yy02NTk2LTQzMmEtYTY2OC0xMzk5YzE2N2QzODYiLCJ0eXAiOiJJRCIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJjbGllbnRJZCI6Im9tcy10ZXN0IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX25hbWUiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3QiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIiwiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3RAcGxhY2Vob2xkZXIub3JnIn0.C0YkK-B4LnKH3NBCxHjuAkVZKVikh3FaUPIUpToCVFKkgefZRF7JS2yddC4ejxn4_B4y56TBMdVSXg5dEk-ghkz_f1VOR1whRY0HAC6Z5izEJBOHesASWoxJE43QJHXoDYzNWJK1S4JQ6W_BF5KobVHrXL2fmb-ypLBJCc8EAMTYEC-fpxT_T3NkDbsAjmnoCTl1YmRiDkV0sqUKerx5irIZJ3S297Z0Ub4Ahal8ObX7t3JbpJ-SBEvRvNo0PriZdk7C1DZQEhc77v2qnpeyqkwcRkAhZ0uXb5QF32J6dxhKh8-gZHYCauMdzeNmkh-962RnWXqyhGOYipLarnmzjg",
    "not-before-policy": 0,
    "session_state": "ea5e1c42-b935-4da0-9d66-a029fdf777b2",
    "scope": "openid email profile"
}

我们可以看到,我们已经获得了访问令牌,可以用作我们平台之间的身份验证和授权令牌。访问令牌的过期时间比刷新令牌的过期时间快,因此可以重新生成访问令牌。

3-应用程序A使用收到的令牌(access_token)向应用程序B发送请求。

HTTP请求的示例:

POST /omsesb/order/placeOrder HTTP/1.1
Host: application-b.it:8081
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg
Client-Id: oms-test
cache-control: no-cache

  {
    "orders": 
        {
            "order": {
                    ...
            }
        }
   }

我们可以看到,“授权”标头包含应用程序A从Keycloak接收到的访问令牌。我们添加了另一个“ Client-Id”标头,其中包含应用程序A请求从Keycloak生成令牌的用户ID。

在此示例中,应用程序A要求应用程序B输入新订单,但必须首先对其自身进行身份验证,因此必须得到授权。

4-应用程序B向Keycloak发出请求,以验证从应用程序A收到的访问令牌。

HTTP请求的示例:

POST /auth/realms/OMS/protocol/openid-connect/token/introspect HTTP/1.1
Host: local-keycloak.it:8080
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache

client_id=account&client_secret=d67da47e-387a-4930-a89a-eda0296c4896&token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg
  • / auth / realms / {REALM_NAME} / protocol / openid-connect / token / introspect: 用于验证令牌的Keycloak端点。

5-此时,Keycloak通过给出验证结果作为响应来进行响应。结果可能是肯定的,也可能是否定的。

包含肯定结果的HTTP响应示例:

{
    "jti": "26b6794d-3d83-446a-a106-10dfb14793c3",
    "exp": 1557997077,
    "nbf": 0,
    "iat": 1557996777,
    "iss": "http://local-keycloak:8080/auth/realms/OMS",
    "aud": "account",
    "sub": "60e8b65c-6596-432a-a668-1399c167d386",
    "typ": "Bearer",
    "azp": "oms-test",
    "auth_time": 0,
    "session_state": "88a63302-b148-42e4-81d0-5cb81c446903",
    "preferred_username": "service-account-oms-test",
    "email": "service-account-oms-test@placeholder.org",
    "email_verified": false,
    "acr": "1",
    "allowed-origins": [
        "http://local-keycloak:8080"
    ],
    "realm_access": {
        "roles": [
            "offline_access",
            "uma_authorization"
        ]
    },
    "resource_access": {
        "account": {
            "roles": [
                "manage-account",
                "manage-account-links",
                "view-profile"
            ]
        }
    },
    "scope": "openid email profile",
    "clientHost": "application-a.it",
    "clientId": "oms-test",
    "user_name": "service-account-oms-test",
    "clientAddress": "80.10.10.1",
    "client_id": "oms-test",
    "username": "service-account-oms-test",
    "active": true
}

我们可以看到,验证成功(active = true):Keycloak识别了令牌(尚未过期),并向应用程序B返回了一组信息,通过该信息,它可以决定是否接受,申请A的请求。

通过此信息,应用程序B能够通过客户端的ID,IP,地址和其他信息来识别先前请求令牌的用户。

在这种情况下,为了避免可能的中间人攻击,B应用程序可以将从应用程序A接收到的数据与Auth Provider接收到的数据进行匹配:它可以验证client-id和IP匹配。

一个包含否定验证结果的HTTP响应示例:

{
    "active": false
}

当令牌不存在,令牌已过期或请求令牌验证的应用程序使用的用户没有请求验证的必要权限时,结果为否。

6--根据从Keycloak接收到的结果,应用程序B与应用程序A进行通信,无论其请求是否已被接受。

那么,您认为此身份验证流程是否正确并因此尊重Oauth2的原则?

3 个答案:

答案 0 :(得分:0)

您能够验证这是否是有效的身份验证流程吗?我在这里发现了类似的内容:Issuing "API keys" using Keycloak

答案 1 :(得分:0)

我没有收到Stack用户的任何答复,但是,在与同事一起查看流程并研究Oauth2时,我们意识到所实现的流程是高效的,并且尊重Oauth2的规则和特性。此外,通过引入基于IP和客户端用户名的最终控制,我们可以确保以下事实:请求令牌的客户端与将令牌作为授权的客户端相同。

答案 2 :(得分:0)

我认为步骤4是不必要的,并且会使Keycloak服务器超载。应用程序B可以自己验证访问令牌。

首先向Keycloak询问其公钥(并存储它们,这样就不必在每次请求时都对其进行查询)。

然后从访问令牌中获取KID并获取相应的公钥。

检查令牌完整性是否安全(验证令牌的签名)。

检查令牌还没有过期。

检查您可能已添加到令牌中的其他任何自定义信息,例如用户角色/权限(如果是这种情况)。