使用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文件如下:
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 Secret和Code替换为您自己的值。返回错误。
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
|