使用Keycloak设置OpenID Connect:分步指南

本文详细介绍了如何使用Keycloak设置OpenID Connect授权码流程,包括Docker容器部署、领域创建、客户端配置、用户管理及令牌交换过程,适合开发者学习身份验证与授权技术。

使用Keycloak设置OpenID Connect:分步指南

本文提供了使用Keycloak设置OpenID Connect授权码流程的全面指导,涵盖关键概念和先决条件。

引言

为应用程序添加安全性几乎总是必要的,但开发者往往觉得难以掌握。因此,理解安全概念至关重要。如今,默认使用OpenID Connect(OIDC)进行身份验证。OIDC是建立在OAuth 2.0之上的一层,负责授权。身份验证是关于识别登录应用程序的人(或系统),而授权是关于在应用程序中被授予的权限。

OAuth 2.0和OIDC的概念在An Illustrated Guide to OAuth and OpenID Connect中有出色解释。强烈建议阅读该文,术语将变得更加清晰。

在本文的剩余部分,您将探索Keycloak,一个广泛使用的身份验证和授权提供者。您将学习如何设置Keycloak并实验授权码流程。

本文使用的源代码可在GitHub上找到。

先决条件

阅读本文的先决条件包括:

  • 了解OAuth 2.0和OpenID Connect,参见之前的参考。另请查看SivaLabs上的精彩系列。
  • Docker(Compose)的基本知识。
  • curl的基本知识。

基本OIDC

如引言所述,请阅读An Illustrated Guide to OAuth and OpenID Connect。作为总结,本段落解释了一些术语。

  • 资源服务器:托管您要访问的API的服务器。
  • 资源所有者:身份的所有者,即如果您是登录者,就是您。
  • 授权服务器:知道资源所有者的服务器,资源所有者在授权服务器有账户。授权服务器通常也是身份验证服务器。
  • 客户端:希望代表资源所有者访问数据的客户端。
  • 客户端ID:用于在授权服务器识别客户端的唯一ID。
  • 客户端密钥:客户端和授权服务器知道的秘密密码。
  • 令牌:存在多种令牌:
    • ID令牌:包含关于身份的以用户为中心的属性(声明),形式为JSON Web Token(JWT)。客户端可以使用此令牌来验证用户身份。
    • 访问令牌:用于访问资源所有者数据的短期令牌。包含范围、权限,有时有限用户信息,但主要关注客户端可以访问的操作或资源。对于Keycloak,这也是JWT形式。
    • 刷新令牌:长期令牌,可用于获取新的访问令牌,无需重新身份验证。
  • 授权码:客户端将用于与授权服务器交换以获取访问令牌的短期令牌。
  • 重定向URI:也称为回调URL,授权服务器将使用此URL向客户端传递数据。
  • 范围:具有特定名称的一组声明。

以下图表解释了OpenID Connect授权码流程。

设置Keycloak

1. 运行容器

启动Keycloak的最简单方法是将其作为容器运行。可以使用以下Docker Compose文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.1.4
    container_name: keycloak
    environment:
      - KC_BOOTSTRAP_ADMIN_USERNAME=admin
      - KC_BOOTSTRAP_ADMIN_PASSWORD=admin
    ports:
      - "8081:8080"
    command: start-dev

启动compose文件如下:

1
docker compose up -d

Keycloak管理控制台可通过http://localhost:8081/admin启动。使用凭据admin/admin登录。不用说,在生产环境中应更改此设置。

2. 创建领域

登录后,您将看到一个master领域。领域对应于租户,因此应首先为租户创建特定领域。点击左上角的master领域,然后点击Create realm按钮。

创建领域mydeveloperplanet并点击Create按钮。

3. 创建客户端

创建一个将使用带有PKCE流程的授权码流程的客户端,这是Web应用程序的标准流程。在左侧菜单中,选择Clients并点击Create client按钮。

选择以下:

  • Client type: OpenID Connect
  • Client ID: application-1(这将识别您的客户端)
  • Name: Application 1

点击Next按钮。选择以下:

  • Client authentication: 启用此选项,它将设置OIDC访问类型为机密,否则适用公共。
  • Authentication flow:
    • Standard flow: 默认已启用,保持原样。这为此客户端启用授权码流程。
    • Direct access grants: 禁用它。

点击Next按钮。选择以下:

  • Valid redirect URIs: http://localhost:8080/callback(这将用于向您的应用程序发送访问令牌)。
  • Web origins: http://localhost:8080(定义请求的来源,假设应用程序在localhost端口8080上运行)。

点击Save按钮,客户端创建完成。

4. 创建用户

您还需要一个用户;否则,没有可登录的内容。在左侧菜单中选择Users并点击Create new user按钮。选择一个用户名并点击Create按钮。

点击Credentials选项卡并点击Set password按钮。选择一个密码并禁用Temporary

在浏览器中打开一个隐身窗口,导航到http://localhost:8081/realms/mydeveloperplanet/account。 填写您的凭据,您必须填写一些必填字段。

点击Submit按钮,您已登录。关闭窗口。

授权码流程

现在是时候看看授权码流程如何工作。请记住插图。

1. 端点

在管理控制台中导航到mydeveloperplanet领域,并在左侧菜单中选择Configure - Realm settings。在底部,您将看到Endpoints和一个指向OpenID Endpoint Configuration的链接。 这将显示一个JSON,其中包含建立连接所需的端点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{

  "authorization_endpoint": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/auth",
  "token_endpoint": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/token",
  "introspection_endpoint": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/token/introspect",
  "userinfo_endpoint": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/userinfo",
  "end_session_endpoint": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/logout",
  "frontchannel_logout_session_supported": true,
  "frontchannel_logout_supported": true,
  "jwks_uri": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/certs",
  "check_session_iframe": "http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/login-status-iframe.html",
  "grant_types_supported": [
    "authorization_code",
    "implicit",
    "refresh_token",
    "password",
    "client_credentials",
    "urn:openid:params:grant-type:ciba",
    "urn:ietf:params:oauth:grant-type:device_code"
  ],
  "acr_values_supported": [
    "0",
    "1"
  ],
  "response_types_supported": [
    "code",
    "none",
    "id_token",
    "token",
    "id_token token",
    "code id_token",
    "code token",
    "code id_token token"
  ],
  ...
}

2. 检索授权码

您首先需要一个授权码。因此,使用授权码端点以及以下信息:

  • Client ID: 在Keycloak中注册的客户端ID。
  • Response Type: 您希望返回授权码。
  • Redirect URI: 在Keycloak中配置的URI。

在浏览器中输入以下URL:

http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/auth?client_id=application-1&response_type=code&redirect_uri=http://localhost:8080/callback&scope=openid

您将被要求输入之前创建的用户的凭据。 因为没有应用程序运行,您将收到一条消息,指出重定向URL的服务器无法访问。但是,请仔细查看浏览器地址栏中的URL。回调URL可见,包括参数中的代码。这是授权码。 URL如下所示:

http://localhost:8080/callback?session_state=8d04f81b-40ec-4957-b822-a2a5b474c0e4&iss=http%3A%2F%2Flocalhost%3A8081%2Frealms%2Fmydeveloperplanet&code=b989b09c-37f6-447d-ab44-ba112e757198.8d04f81b-40ec-4957-b822-a2a5b474c0e4.ff25ccf8-6faf-4fe2-8fe2-03813df493ed

3. 检索访问令牌

客户端现在能够联系授权服务器(Keycloak)并检索访问令牌。使用令牌端点以及以下信息:

  • Grant Type: 您正在使用授权码。
  • Client ID: 在Keycloak中注册的客户端ID。
  • Client Secret: 在管理控制台中,导航到mydeveloperplanet领域,选择application-1客户端,并点击Credentials选项卡。客户端密钥在此可用。
  • Code: 从上一段返回的授权码。
  • Redirect URI: 在Keycloak中配置的URI。

在终端(非浏览器)中,输入以下命令。将Client SecretCode替换为您自己的值。返回错误。

1
2
3
4
5
6
7
8
9
$ curl -X POST \
"http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=application-1" \
-d "client_secret=U5UCgtTzrYbxKJ3aiDnre7WdPIwufFmp" \
-d "code=b989b09c-37f6-447d-ab44-ba112e757198.8d04f81b-40ec-4957-b822-a2a5b474c0e4.ff25ccf8-6faf-4fe2-8fe2-03813df493ed" \
-d "redirect_uri=http://localhost:8080/callback"
{"error":"invalid_grant","error_description":"Code not valid"}

如果您已执行本文中的步骤,可能花费了一些时间。因此,授权码不再有效。 检索一个新的授权码,这次要足够快。

1
2
3
4
5
6
7
8
9
$ curl -X POST \
"http://localhost:8081/realms/mydeveloperplanet/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=application-1" \
-d "client_secret=U5UCgtTzrYbxKJ3aiDnre7WdPIwufFmp" \
-d "code=81b9deab-20cb-4fdd-8256-c889c0b6ab07.8d04f81b-40ec-4957-b822-a2a5b474c0e4.ff25ccf8-6faf-4fe2-8fe2-03813df493ed" \
-d "redirect_uri=http://localhost:8080/callback"
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ2Y2JkZjUyRDY5SmNDUnJBQ2dmU0wyc0FPVlBRMWVIUDJBamoxVFF3a1VJIn0.eyJleHAiOjE3NDQ0NTM1OTgsImlhdCI6MTc0NDQ1MzI5OCwiYXV0aF90aW1lIjoxNzQ0NDUyNTAyLCJqdGkiOiI1ZWNhMjNmYi0yNzZiLTQzOTItYWJkZS04ZmNkZTNiZGUyNDQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODEvcmVhbG1zL215ZGV2ZWxvcGVycGxhbmV0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImY2ZjE0MGMyLTVjOGQtNDUzMi1iOGMxLTY3MWY2MmJjZDVkMSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcGxpY2F0aW9uLTEiLCJzaWQiOiI4ZDA0ZjgxYi00MGVjLTQ5NTctYjgyMi1hMmE1YjQ3NGMwZTQiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLW15ZGV2ZWxvcGVycGxhbmV0IiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiR3VudGVyIE15ZGV2ZWxvcGVycGxhbmV0IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ3VudGVyIiwiZ2l2ZW5fbmFtIjoiR3VudGVyIiwiZmFtaWx5X25hbWUiOiJNeWRldmVsb3BlcnBsYW5ldCIsImVtYWlsIjoiYS5hQGV4YW1wbGUuY29tIn0.iYuKMBNOZwhANDkhqfGsno8tt8uHMZ-1EL5Z3Lmhpq93iRP6bdSNK3sbyoEtpHIrov4iB5_hh1EMSseYVgLivkx2rQ7FK0S6fM8paeChb3c3A4RWROtCr9oQY7Wqs1-CkZXr5dd0OmxHt89AY2KvmCz_c0LSEhT5Y4P_cf7kuvwHCjeHMHsw7cuJ3_dajGWEBqBcA--28HQ5uwKoYjOheu_vL-Sodi5cqxCLH0AGuFjbTQg748F296VRVYGaZzk3OeAudjB4neI4WErZYzufxhdBn1ZNTzAxPdAEDGqJ28BEwCqMxfw4KJmsMPdQcftWChGqEAPvxToYR7u3rvhZuA","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2YzU3YjY2ZC0zZGRmLTQzNDItYTU3Mi0xODM3OTRiNDM3YTYifQ.eyJleHAiOjE3NDQ0NTUwOTgsImlhdCI6MTc0NDQ1MzI5OCwianRpIjoiNjJkOTUxOTMtNDk4Zi00ZWU4LWEzNjgtNjQzNDQ5ZmYwNWZmIiwiaXNzIjoiaHR0cDovL2xvY
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计