IdentityServer4 之Client Credentials走起來
阿新 • • 發佈:2021-01-04
### 前言
API裸奔是絕對不允許滴,之前專門針對這塊分享了jwt的解決方案([WebApi介面裸奔有風險](http://mp.weixin.qq.com/s?__biz=MzU1MzYwMjQ5MQ==&mid=2247484105&idx=1&sn=9322333e9fc39a0a0daf2c4901cf1efd&chksm=fbf11e1dcc86970be6a815b946d43b0f6753f89d3fb6b447b21a1e10373be44cbc59e0ebfe15&token=2038876923&lang=zh_CN#rd));那如果是微服務,又怎麼解決呢?每一個服務都加認證授權也可以解決問題,只是顯得認證授權這塊冗餘,重複在搞事情;IT大佬肯定容忍不了,對於微服務架構,統一的認證授權中心那是必須的。
隨著.NetCore的釋出,IdentityServer4隨之而出,是.Net Foundation的成員之一,專門針對.NetCore而出的認證授權框架,當前.Net圈是比較火的啦;再配上微服務認證授權的必要性,我決定以此開始入手進行微服務架構學習分享;
主要的學習分享思路為敲程式碼為嚮導,如果遇到相關理論概念,結合程式碼案例進行解釋,不在單獨針對理論知識整理相關文章(主要是擔心歸納總結不好,讓小夥伴疑惑,所以就想著結合應用案例解釋比較容易理解)。
### 正文
IdentityServer4 主要的功能就是認證和授權,其他功能這裡先假裝不知道;主要目的就是想用其統一保護各個微服務的介面;先來理解一下認證和授權:
- **授權(Authorization)**:在使用者身份認證通過之後,授予使用者訪問資源的過程或是使用者授予第三系統訪問自己資源的過程,資源可能是個人資訊、檔案、資料、介面等;OAuth2是現在比較火的授權標準,對於授權流程,後續會舉例說明;
在公司,假如小夥伴是領導,在出差或休假的時候,通常會通過口頭、郵件、資訊等方式將一些工作臨時委託給某人處理,比如簽字、參會等,這個過程叫做授權,如果沒有授權,簽字無效,也不能隨意參會;
- **認證(Authentication)**:使用者身份認證,可以將其理解為登入;系統驗證身份憑據是否合法,比如使用者名稱/密碼、人臉識別等方式;OpenId Connect是目前比較流行的身份認證標準協議,**OpenID**是一個去中心化的網上身份認證系統,OpenID Connect是在OAuth2基礎進行擴充套件,增加身份認證和相關身份標識資訊;
稍微有點規模的公司,通常都有自己的辦公樓,有專門的保安人員,管控非公司人員的進入, 如果是公司人員,刷卡識別即可進入,如果是非公司人員需要登記個人資訊確認才能進入,這個過程可以理解為身份認證;只有驗證資訊之後才能進入公司。
**IdentityServer4** 已經將OpenID Connect和OAuth 2.0封裝實現,開發者開箱即用,無需再重新自己實現細節,但如果有需要,小夥伴可以在IdentitySever4基礎進行擴充套件個性化需求;
在授權過程中,根據應用場景不同,有四種授權模式可以選擇,如下:
1. **Authorization Code(授權碼)**:最完整的授權模式,也是相對比較安全的模式,適用於有後臺的應用程式,如MVC專案;
2. **Implicit(簡化模式)**:簡化授權碼模式,適用於無後臺的應用程式,如前後端分離專案;
3. **Resource Owner Password Credentials(資源所有者密碼)**:直接通過使用者名稱和密碼獲得授權,這種適用於高度信任的應用,因為需要輸入使用者名稱和密碼,安全洩露風險高;
4. **Client Credentials(客戶端模式) **:這是無使用者操作模式,適用於機器對機器的對接,沒有使用者干預的應用,如後臺任務排程應用,採集資料應用等;
5. 混合模式:以上四種的組合。
其他理論先不說了,我們邊擼碼邊聊,這樣記憶深刻一點,這裡就從最簡單的**Client Credentials**開始:
#### Client Credentials 客戶端授權模式
客戶端模式沒有使用者,就只是單純的機器對機器的互動,大概的流程如下:
![image-20201230210937580](https://i.loli.net/2020/12/30/5Ryp4PkKxWZFus2.png)
流程簡要說明:
1. 首先客戶端帶上憑據向授權伺服器獲取AccessToken,這裡的客戶端憑據是提前在授權伺服器上備案過的;
2. 授權伺服器驗證客戶端憑據,成功之後直接返回AccessToken;
3. 客戶端在帶上AccessToken訪問資源伺服器;
4. 資源伺服器正常返回結果,如果沒有AccessToken是不能訪問受保護資源的;
來,結合流程看看程式碼怎麼實現,一步一步來:
##### >>>先建立API專案---資源伺服器
1. 建立一個OrderController,並在裡面新增一個Orders 介面,介面沒有進行保護;
![image-20201231105706789](https://i.loli.net/2020/12/31/hfIcS3KX2yQNM85.png)
2. 介面沒有進行保護,可以任意訪問,如下:
![image-20201231105517893](https://i.loli.net/2020/12/31/uRtbzvEjlCZhi26.png)
##### >>>再建立認證授權中心專案---授權伺服器,將資源服務保護起來
以上的API介面裸奔是有風險的,現在需要統一的認證授權中心進行保護,如下:
1. 新建立一個API專案,並引入IdentityServer4包,並在記憶體中模擬相關資料,方便測試;
![image-20201231123450257](https://i.loli.net/2020/12/31/xNHPjDcASQdGuzp.png)
**術語解釋**:
ApiScope:就是一個作用域範圍,生成的Token只能訪問指定範圍的資源;
Client:這裡的客戶端就是應用,比如MVC專案、純前端專案、Winfrom/WPF、APP等,必須首先在授權伺服器中進行備案並獲得授權伺服器分配的標識和密碼,後續用於獲取AccessToken;
2. 模擬資料準備好了,就在Startup中進行對應的注入和配置,並開啟中介軟體,如下:
![image-20201231124703423](https://i.loli.net/2020/12/31/Gt326EvAcfJ7prV.png)
3. 這樣就初步完成授權伺服器的搭建,這裡監聽的埠改為6100了,用Postman先來測測是否能正常獲取Token,如下:
![image-20201231133328069](https://i.loli.net/2020/12/31/eEDax9CdpXPn3ZY.png)
可能有新手小夥伴會問,咋知道是這個地址能獲取token的? 小夥伴可以在瀏覽器中輸入以下連結,即可看見授權伺服器的相關資訊(授權伺服器地址+/.well-known/openid-configuration):
![image-20201231133558917](https://i.loli.net/2020/12/31/rlXFQYHTUiuZhaD.png)
4. 授權伺服器已經好了,準備將資源伺服器接入到授權伺服器,對API介面進行保護(ApiDemo專案中),如下:
![image-20201231135258124](https://i.loli.net/2020/12/31/gd327qaYpHkGD5F.png)
注:ApiDemo專案中需要Microsoft.AspNetCore.Authentication.JwtBearer包,因為專案是基於.NetCore3.1的,所以這裡引用的包版本為3.1.10。
5. 然後在介面上面加上[Authorize]特性,將介面保護起來,看執行效果如下:
![image-20201231135953125](https://i.loli.net/2020/12/31/QJy2oSqYeRi8uAh.png)
6. 在Postman中測試,先獲取AccessToken,然後將獲取的AccessToken加入到Header中請求資源伺服器中受保護的API,如下:
![image-20201231140952036](https://i.loli.net/2020/12/31/QMfwyzPJrVRD93u.png)
##### >>>真實客戶端訪問受保護API---控制檯
建一個控制檯專案,具體步驟如圖:
![image-20201231144413626](https://i.loli.net/2020/12/31/3cbQImu9p2RAYaU.png)
這裡就不用文字說明步驟,小夥伴一邊看程式碼,一邊看註釋,這樣應該比較清晰點:
```c#
static async Task Main(string[] args)
{
// 1. 建立一個HttpClient用於請求
var client = new HttpClient();
// 2. 獲取授權伺服器的相關資訊,IdentityModel已經將其封裝好了
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:6100");
// 3. 檢查是否請求錯誤
if (disco.IsError)
{
// 錯誤就列印錯誤資訊,然後直接返回
Console.WriteLine(disco.Error);
return;
}
// 4. 通過授權服務分配的標識,向授權伺服器請求AccessToken
var tokenResp = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
// 指定獲取token的地址,IdentityModel進行封裝,直接使用即可
Address = disco.TokenEndpoint,
// 指定授權伺服器分配的客戶端標識
ClientId = "client",
// 指定授權伺服器分的客戶端密碼
ClientSecret = "ordersecret"
});
// 5. 檢查獲取Token是否成功
if (tokenResp.IsError)
{
// 如果失敗,列印錯誤訊息並返回
Console.WriteLine(tokenResp.Error);
return;
}
// 6. 建立一個請求API資源的HttpClient
var apiClient = new HttpClient();
// 7. 將獲取到的Token以Bearer的方案設定在請求頭中
apiClient.SetBearerToken(tokenResp.AccessToken);
// 8. 向資源伺服器中請求受保護的API
var contentResp = await apiClient.GetAsync("http://localhost:5000/api/Order");
// 9. 列印對應的訊息
if (contentResp.IsSuccessStatusCode)
{
var content = await contentResp.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
else
{
Console.WriteLine(contentResp.StatusCode);
}
Console.ReadLine();
}
```
到這裡離完成還差一步了,什麼,資源不是保護了嗎,受保護資源也能正常訪問了,還差哪一步?
在授權伺服器模擬備案客戶端的時候,是不是指定了訪問資源的作用域,也就是說,備案過的客戶端只能訪問被授權的API資源,而現在拿到的AccessToken都能訪問資源伺服器中所有受保護的資源,那是因為資源伺服器中的API資源沒有限制作用域訪問,而在實際專案中,並不是拿到AccessToken就能隨便訪問,需要做限制,繼續往下看↓↓↓
![image-20201231152850521](https://i.loli.net/2020/12/31/uKtWILA76ZkobFn.png)
假如指定的scope值和客戶端在授權伺服器中備案時設定的不一樣,就算獲取到AccessToken也不能正常訪問資源,會報403錯誤,這裡我不截圖,小夥伴下去試試。
可能小夥伴會比較急,這都是啥玩意,全是硬編碼,垃圾文; 別別別,說好的學習分享嘛,一步一個腳印來嘛,最終肯定是小夥伴想要的,也是我學習的目標;
關於客戶端憑據生成的Token,在jwt.io網站解析看看,記錄一下,看看後面有使用者參與的情況,生成的Token解析出來會有什麼不同呢,先上個圖(圖中解析出來的屬性之前在[WebApi介面裸奔有風險](http://mp.weixin.qq.com/s?__biz=MzU1MzYwMjQ5MQ==&mid=2247484105&idx=1&sn=9322333e9fc39a0a0daf2c4901cf1efd&chksm=fbf11e1dcc86970be6a815b946d43b0f6753f89d3fb6b447b21a1e10373be44cbc59e0ebfe15&token=2038876923&lang=zh_CN#rd)有說過):
![image-20201231154747613](https://i.loli.net/2020/12/31/zw5ixteBrAp2O6b.png)
### 總結
從這篇開始,後續會盡快更新學習分享,小夥伴們加入一起學習,一起討論。下一篇說說**Resource Owner Password Credentials**.
一個被程式搞醜的帥小夥,關注"Code綜藝圈",跟我一起學~
![](https://i.loli.net/2020/12/31/jVvHUbmiocFW