1. 下载并安装 Keycloak
- 下载地址
- 这里以版本 7 为例子
- 解压,点击 binstandalone.bat 直接运行即可
- 默认登陆地址 http://localhost:8080/auth
- 创建账号并登陆
- 官方文档
https://www.keycloak.org/docs/latest/getting_started/index.html
https://www.keycloak.org/docs/latest/securing_apps/index.html
2. 创建 Realm,Client,User
- Keycloak 有一个默认的 Master Realm,自己再创建一个 Test Realm
- 在 Test Realm 下创建一个命名为 resource_01 的 client
- 进入 resource_01 的 Settings 页,做以下配置
(1) 将 Access Type 改为 confidential
(2) 改为 confidential 后会多出 Authorization Enabled 选项,将其设置为 ON
(3) Valid Redirect URIs 填 *
配好后点击 Save 保存 - 进入 resource_01 的 Credentials 页,记下 Secret 的值,后面访问这个 client 的时候会用到
- 进入 resource_01 的 Roles 页,添加两个 roles,分别命名为 admin 和 operator
- 进入 resource_01 的 Settings 页,做以下配置
- 在 Test Realm 下创建一个 user_01 的 user
- 进入 user_01 的 Credentials 页,将 Temporary 设置为 OFF,然后重置密码
- 将 client 的 role 和 user 关联
- 进入 user_01 的 Role Mappings 页,在 Client Roles 选 resource_01,在 Available Roles 选择 admin,点 Add Selected,意味着 user_01 可以访问 resource_01,并且是 admin 权限,可以再创建一个 user_02 将 role 选为 operator,意味着 user_02 可以访问 resource_01,但只有做基本操作的权限(创建 client 的 role 的时候名字是可以随便写的,严格来讲 resource_01,admin 和 operator 都是什么含义,应该由 server 决定)
- client 的 role 也可以和其他 client 关联 (这篇文章里没用上)
- 进入 client 的 Service Account Roles 页,在 Client Roles -> Available Roles 选择
3. 通过 Keycloak 做验证
一般场景是三个节点:客户端,服务端,Keycloak
客户端通过 Keycloak 获取一个 Token,再用这个 Token 访问服务端,服务端对 Token 做验证
这里用 Postman 模拟客户端,假设要访问的服务端的 URL 是 192.168.1.1:9090/api/server-name/v1/service-01
- 在 Authorization -> TYPE,选 OAuth 2.0
- 点 Get New Access Token,各项配置如下
- Grant Type:Password Credentials
- Access Token URL:http://localhost:8080/auth/realms/Test/protocol/openid-connect/token
- Username:user_01
- Password:user_01 的密码
- Client ID:resource_01
- Client Secret:在 resource_01 的 Credentials 页下找
- Scope:Realm Setting -> General -> Endpoints 点 OpenID Endpoint Configurations 找到 scopes_supported 随便选一个填比如 openid
- Client Authentication :Send as Basic Auth Header (意思是 Postman 拿到 Token 后是放在 Header 发给服务端)
点 Request Token,成功获取后点 Use Token
- 发送请求给服务端
实际上发送请求的时候,加上了一个 name 为 Authorization,value 为 Bearer ${token} 的 Header 项
以上操作用命令行执行的话命令如下
curl -k -X POST -d "grant_type=password&username=user_01&password=123456&scope=%22openid%22&client_id=resource_01&client_secret=d54d22b0-4941-415d-ba59-79b7ce70498e" http://localhost:8080/auth/realms/Test/protocol/openid-connect/token
curl -X GET -H "X-Request-With: XMLHttpRequest" -H "content-type: application/json" -H "Authorization: Bearer xxx" -H "api_key: queryOverdueInfo" http://192.168.1.1:9090/api/server-name/v1/service-01
4. Token 的内容
JWT (Json Web Token) 由三部分组成:header,payload,signature
从 Keycloak 获取到的 token = base64(header) + "." + base64(payload) + "." + signature
其中 signature = encrypt( base64(header) + base64(payload) , privateKey)
可以看到,token 的 header 和 payload 相当于是明文的,只要对相应部分进行解码就可以看到
服务器端主要用 publicKey 对 signature 部分解密,然后和 header,payload 部分对比,确保 token 是合法的
每一个 Realm 会有自己的 privateKey 和 publicKey
对 header 部分和 payload 部分解码,内容如下
header
{
"alg" : "RS256",
"typ" : "JWT",
"kid" : "jwQF3Y_V5vCfNSkKtP5XAt9LzdpKKjfjGOEFPZsw8xc" // key id,密钥 id
}
payload
{
"jti":"df57c8b5-9801-47b3-9977-74a66e9a8d88", // jwt id
"exp":1580829682, // 过期时间
"nbf":0, // 有效起始时间
"iat":1580829382, // 发行时间
"iss":"http://localhost:8080/auth/realms/Test", // 发行者
"aud":"account", // 受众
"sub":"0d61a540-4914-4887-ac28-87b21da91da3", // 主题
"typ":"Bearer",
"azp":"resource_01", // Authorized party
"auth_time":0,
"session_state":"1a8a0680-5e6e-40dc-a150-9a84954dbc55",
"acr":"1",
"realm_access": {
"roles":[
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"resource_01": {
"roles":[
"admin"
]
},
"account":{
"roles":[
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope":"openid email profile",
"email_verified":false,
"preferred_username":"user_01"
}
服务端验证并解码 token 就可以看到,这个 token 的 username 是 user_01,它有 resource_01 这个 client 的 admin 权限,服务端就可以根据 resource_01,admin 决定是否允许做相应的操作,当然也可以使用其它字段,这都是由服务端自己决定的
在请求 token 的时候,client id 不一定要填 user_01 关联的 client,比如可以填上 resource_02 及相应的 secret,这也是允许的,这样得到的 token,resource_access 部分依然是 resource_01 以及 admin(假设 user_01 没有同时关联 resource_02),但 azp 部分会变成 resource_02,这种用法的其中一种场景,是可以有一个统一的 client 做入口,比如有一个 client 就叫 resources,不论哪个 user 要访问哪个 resource,获取 token 的时候 client 都统一填 resources 就可以,因为 token 里的 resource_access 字段依然是 user 真正关联的所有 client
如果请求 token 的时候,Grant Type 选的是 Client Credentials,这样只需要填 client id 和 client secret,不需要填 user 和 password,这样得到的 token,以 resource_01 为例子,会多一个 clientId 字段为 resource_01,而 preferred_username 部分将变成 service-account-resource_01,resource_access 部分还是 resource_01,roles 变成 uma_protection(这是创建 client 时默认就有的 role,如果把这个 role 删除了,那么返回的 token 的 resource_access 字段不会有 resource_01 的信息)
5. Springboot 验证 Token
如果服务端是 Springboot,那么有两种方法验证
-
继承 ResourceServerConfigurerAdapter 类,这是一个通用的做 OAUTH 2.0 验证的类,验证的 Token 可以不是 Keycloak 发的,只要是符合标准的 Token 就可以,需要配置包括 Realm 的 PublicKey 在内的一些信息,做验证的时候 keycloak 可以没运行,只要 PublicKey 能用于解密就可以
-
继承 KeycloakWebSecurityConfigurerAdapter 类,这是 springboot 做 keycloak 验证的适配器,需要配 keycloak 的地址和相应的 Realm,不用配 PublicKey,springboot 自己会去拿,第一次验证的时候 keycloak 必须在运行,后续的验证不需要
6. 通过 REST API 操作 Keycloak
可用的 API:https://www.keycloak.org/docs-api/7.0/rest-api/index.html
要通过 API 操作,同样需要通过 user 拿先一个 Token,再用这个 Token 去操作 Keycloak,这个 user 要有相应的权限
- 创建一个 user 命名为 admin_user
- 在 Role Mappings 的 Client Roles 选择 realm-management,把 Available Roles 都加上
- 取 Token 的时候 client_id 填 admin-cli
- curl 命令如下
curl -k -X POST -d "grant_type=password&username=admin_user&password=123456&client_id=admin-cli" http://localhost:8080/auth/realms/Test/protocol/openid-connect/token