当后端在 Docker 容器中 运行 时,Keycloak 令牌验证失败

Keycloak token verification fails when the backend is running in a Docker container

我正处于构建 Web 应用程序的早期阶段。我打算使用 Keycloak 作为身份提供者来保护后端。在我的本地机器上,我 运行 Keycloak 和我的后端都是 docker 容器,但在不同的网络上,因为最终在生产中,我想要认证服务器 运行 Keycloak 运行 与后端分开,例如 account.example.comapi.example.com 分别

在本地,我的 Keycloak 容器可以通过基础 URL http://localhost:8080/auth 和后端通过 http://test.localhost:8000/

访问

我在 Keycloak 领域创建了一个客户端,其访问类型是保密的。我正在使用授权代码授予类型生成令牌。

因此,后端的每个 REST API 端点都会验证传递给授权的令牌 header,然后在处理请求之前调用 Keycloak 服务器来验证令牌。

我目前遇到的问题是令牌验证失败并响应

{"error":"invalid_token","error_description":"Token verification failed"}'

经过调查,显然是因为我从后端 API 容器调用 Keycloak 服务器。如果我在后端 docker 容器中使用 curl 生成令牌,我收到的令牌被验证正常,但在容器外部生成的令牌却没有。

我正在使用 python-keycloak 作为 Keycloak REST 的包装器 API

from keycloak import KeycloakOpenID


self._keycloak = KeycloakOpenID(
            server_url='http://host.docker.internal:8080/auth/',
            realm_name='myrealm',
            client_id='myclient',
            client_secret_key='mysecret,
        )

if "HTTP_AUTHORIZATION" not in request.META:
       
        return JsonResponse(
            {"detail": NotAuthenticated.default_detail},
            status=NotAuthenticated.status_code,
        )

 auth_header = request.META.get("HTTP_AUTHORIZATION").split()
 token = auth_header[1] if len(auth_header) == 2 else auth_header[0]

    
    try:
        self.keycloak.userinfo(token)
    except KeycloakInvalidTokenError as e:
        # print(e)
        return JsonResponse(
            {"detail": AuthenticationFailed.default_detail},
            status=AuthenticationFailed.status_code,
        )

如何解决此问题并在我的本地计算机上进行令牌验证

问题出在令牌的 issuer 上。邮递员的令牌颁发者是 http://localhost:8080/...,但后端配置为仅接受颁发者 http://host.docker.internal:8080/...。最佳做法是在任何地方对 IdP(在您的情况下为 Keycloak)使用相同的 protocol:domain[:port],例如https://keycloak.domain.com,否则就会出现这种问题

您的令牌无效,因为令牌中的颁发者 (iss) 与您的后端服务期望的颁发者不匹配。

您的后端(或后端中的 adapter/framework)将使用 OIDC 发现协议来确定预期的发行者。为此,它将调用 https://keycloak-container-name/auth/realms/<your-realm>/.well-known/openid-configuration。这将 return 像这样的元数据:

{
  "issuer":"https://keycloak-container-name/auth/realms/<your-realm>",
  ...
}

Keycloak 将根据请求确定颁发者的主机部分(在本例中为keycloak-container-name)。因此,如果您的后端从 docker 网络中使用 keycloak-container-name 查询发现端点,则主机部分将为 your-container-name。在这种情况下,您的后端将期望发行人为 https://keycloak-container-name/auth/realms/<your-realm>

现在,如果您想从前端查询令牌,您的前端会将请求发送到 http://localhost:8080/auth/...。由于发行人将根据请求确定,因此在这种情况下,该令牌中的发行人将为 https://localhost:8080/auth/realms/<your-realm>。 这与预期的发行者不匹配 https://keycloak-container-name/auth/realms/<your-realm>,因此令牌将被拒绝为无效。

您还可以通过 http://localhost:8080/auth/realms/<your-realm>/.well-known/openid-configuration 调用 OIDC 发现端点来验证这一点。您将收到这样的回复:

{
  "issuer":"http://localhost:8080/auth/realms/<your-realm>",
  ...
}

要解决此问题,您可以将您领域中的 Frontend URL 设置为 http://localhost:8080/auth。使用此设置,发行者将不再在请求中确定,而是固定 http://localhost:8080/auth/realms/<your-realm>。 您可以通过向 https://keycloak-container-name/auth/realms/<your-realm>/.well-known/openid-configuration 发出请求从您的后端容器中检查这一点。现在 return 元数据如下:

{
  "issuer":"http://localhost:8080/auth/realms/<your-realm>",
  ...
}

如果您不想为每个领域单独配置它,您可以改为配置 default hostname provider 服务器范围。