1. 下載並安裝 Keycloak
- 下載地址
- 這里以版本 7 為例子
- 解壓,點擊 bin\standalone.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
