OIDC在 ASP.NET Core中的应用
我們?cè)凇禔SP.NET Core項(xiàng)目實(shí)戰(zhàn)的課程》第一章里面給identity server4做了一個(gè)全面的介紹和示例的練習(xí) 。
如果想完全理解本文所涉及到的話題,你需要了解的背景知識(shí)有:
什么是OpenId Connect (OIDC)
OIDC 對(duì)oAuth進(jìn)行了哪些擴(kuò)展?
Identity Server4提供的OIDC認(rèn)證服務(wù)(服務(wù)端)
ASP.NET Core的權(quán)限體系中的OIDC認(rèn)證框架(客戶端)
什么是 OIDC
在了解OIDC之前,我們先看一個(gè)很常見(jiàn)的場(chǎng)景。假使我們現(xiàn)在有一個(gè)網(wǎng)站要集成微信或者新浪微博的登錄,兩者現(xiàn)在依然采用的是oAuth 2.0的協(xié)議來(lái)實(shí)現(xiàn) 。 關(guān)于微信和新浪微博的登錄大家可以去看看它們的開(kāi)發(fā)文檔。
在我們的網(wǎng)站集成微博或者新浪微博的過(guò)程大致是分為五步:
準(zhǔn)備工作:在微信/新浪微博開(kāi)發(fā)平臺(tái)注冊(cè)一個(gè)應(yīng)用,得到AppId和AppSecret
發(fā)起 oAauth2.0 中的 Authorization Code流程請(qǐng)求Code
根據(jù)Code再請(qǐng)求AccessToken(通常在我們應(yīng)用的后端完成,用戶不可見(jiàn))
根據(jù) AccessToken 訪問(wèn)微信/新浪微博的某一個(gè)API,來(lái)獲取用戶的信息
后置工作:根據(jù)用戶信息來(lái)判斷是否之前登錄過(guò)?如果沒(méi)有則創(chuàng)建一個(gè)用戶并將這個(gè)用戶作為當(dāng)前用戶登錄(我們自己應(yīng)用的登錄邏輯,比如生成jwt),如果有了則用之前的用戶登錄。
中間第2到3的步驟為標(biāo)準(zhǔn)的oAuth2 授權(quán)碼模式的流程,如果不理解的可以參考阮一峰所寫的《理解oAuth2.0?》一文。我們主要來(lái)看第4和5步,對(duì)于第三方應(yīng)用要集成微博登錄這個(gè)場(chǎng)景來(lái)說(shuō)最重要的是我希望能快速拿到用戶的一些基本信息(免去用戶再次輸入的麻煩)然后根據(jù)這些信息來(lái)生成一個(gè)我自己的用戶跟微博的用戶Id綁定(為的是下次你使用微博登錄的時(shí)候我還能把你再找出來(lái))。
oAuth在這里麻煩的地方是我還需要再請(qǐng)求一次API去獲取用戶數(shù)據(jù),注意這個(gè)API和登錄流程是不相干的,其實(shí)是屬于微博開(kāi)放平臺(tái)叢多API中的一個(gè),包括微信開(kāi)放平臺(tái)也是這樣來(lái)實(shí)現(xiàn)。這里有個(gè)問(wèn)題是前面的 2和3是oAuth2的標(biāo)準(zhǔn)化流程,而第4步卻不是,但是大家都這么干(它是一個(gè)大家都默許的標(biāo)準(zhǔn))
于是大家干脆就建立了一套標(biāo)準(zhǔn)協(xié)議并進(jìn)行了一些優(yōu)化,它叫OIDC
OIDC 建立在oAuth2.0協(xié)議之上,允許客戶端(Clients)通過(guò)一個(gè)授權(quán)服務(wù)(Authorization Server)來(lái)完成對(duì)用戶認(rèn)證的過(guò)程,并且可以得到用戶的一些基本信息包含在JWT中。
OIDC對(duì)oAuth進(jìn)行了哪些擴(kuò)展?
在oAuth2.0授權(quán)碼模式的幫助下,我們拿到了用戶信息。
上沒(méi)有認(rèn)證的過(guò)程,只是給我們的應(yīng)用授權(quán)訪問(wèn)一個(gè)API的權(quán)限,我們通過(guò)這個(gè)API去獲取當(dāng)前用戶的信息,這些都是通過(guò)oAuth2的授權(quán)碼模式完成的。 我們來(lái)看看oAuth2 授權(quán)碼模式的流程:
第一步,我們向authorize endpoint請(qǐng)求code的時(shí)候所傳遞的response_type表示授權(quán)類型,原來(lái)只有固定值code
GET authorize?response_type=code&client_id=postman&state=xyz&scope=api1 &redirect_uri=http://localhost:5001/oauth2/callback第二步,上面的請(qǐng)求執(zhí)行完成之后會(huì)返回301跳轉(zhuǎn)至我們傳過(guò)去的redirect_uri并帶上code
https://localhost:5001/oauth2/callback?code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c&scope=api1&state=111271607第三步,用code換取access token
POST token?grant_type=authorization_code&code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c&redirect_uri=http://localhost:5001/oauth2/callback&client_id=postman&client_secret=secret通過(guò)這個(gè)POST我們就可以得到access_token
{ "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjV", "expires_in": 3600, "token_type": "Bearer"}我們拿到access_token之后,再把a(bǔ)ccess_token放到authorization頭請(qǐng)求 api來(lái)獲取用戶的信息。在這里,這個(gè)api不是屬于授權(quán)服務(wù)器提供的,而是屬于資源服務(wù)器。
OIDC給oAuth2進(jìn)行擴(kuò)展之后就填補(bǔ)了這個(gè)空白,讓我們可以授權(quán)它添加了以下兩個(gè)內(nèi)容:
response_type 添加IdToken
添加userinfo endpoint,用idToken可以獲取用戶信息
OIDC對(duì)它進(jìn)行了擴(kuò)展,現(xiàn)在你有三個(gè)選擇:code, id_token和 token,現(xiàn)在我們可以這樣組合來(lái)使用。
| code | Authorization Code Flow |
| id_token | Implicit Flow |
| id_token token | Implicit Flow |
| code id_token | Hybrid Flow |
| code token | Hybrid Flow |
| code id_token token | Hybrid Flow |
我們簡(jiǎn)單的來(lái)理解一下這三種模式:
Authorization Code Flow授權(quán)碼模式:保留oAuth2下的授權(quán)模式不變r(jià)esponse_type=code
Implicit Flow 隱式模式:在oAuth2下也有這個(gè)模式,主要用于客戶端直接可以向授權(quán)服務(wù)器獲取token,跳過(guò)中間獲取code用code換accesstoken的這一步。在OIDC下,responsetype=token idtoken,也就是可以同時(shí)返回access_token和id_token。
Hybrid Flow 混合模式: 比較有典型的地方是從authorize endpoint 獲取 code idtoken,這個(gè)時(shí)候id_token可以當(dāng)成認(rèn)證。而可以繼續(xù)用code獲取access_token去做授權(quán),比隱式模式更安全。
再來(lái)詳細(xì)看一下這三種模式的差異:
| access token和id token都通過(guò)Authorization endpoint返回 | no | yes | no |
| 兩個(gè)token都通過(guò)token end point 返回 | yes | no | no |
| 用戶使用的端(瀏覽器或者手機(jī))無(wú)法查看token | yes | no | no |
| Client can be authenticated | yes | no | yes |
| 支持刷新token | yes | no | yes |
| 不需要后端參與 | no | yes | no |
我們來(lái)看一下通過(guò)Hybird如何獲取 code、id_token、_以及access_token,然后再用id_token向userinfo endpoint請(qǐng)求用戶信息。
第一步:獲取code,
response_type=code id_token
scope=api1 openid profile 其中openid即為用戶的唯一識(shí)別號(hào)
id=postman&state=xyz&scope=api1 openid profile&nonc
e=7362CAEA-9CA5-4B43-9BA3-34D7C303EBA7 &r
edirect_uri=http://localhost:5001/oauth2/callback
當(dāng)我們使用OIDC的時(shí)候,我們請(qǐng)求里面多了一個(gè)nonce的參數(shù),與state有異曲同工之妙。我們給它一個(gè)guid值即可。
第二步:我們的redirect_uri在接收的時(shí)候即可以拿到code 和 id_token
https://localhost:5001/oauth2/callback#code=c5eaaaca8d4538f69f670a900d7a4fa1d1300b26ec67fba2f84129f0ab4ffa35&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjVjMzA5ZGIwYTE2OGEwOTgGtpbj0GVXNnkKhGdrzA&scope=openid%20profile%20api1&state=111271607第三步:用code換access_token(這一步與oAuth2中的授權(quán)碼模式一致)
第四步:用access_token向userinfo endpoint獲取用戶資料
Get http://localhost:5000/connect/userinfo Authorization Bearer access_token返回的用戶信息
{ "name": "scott", "family_name": "liu", "sub": "5BE86359-073C-434B-AD2D-A3932222DABE"}以下是我們的流程示意圖。
可能會(huì)注意到,在這里我們拿到的idtoken沒(méi)有派上用場(chǎng),我們的用戶資料還是通過(guò)access_token從userinfo endpoint里拿的。這里有兩個(gè)區(qū)別:
userinfo endpoint是屬于認(rèn)證服務(wù)器實(shí)現(xiàn)的,并非資源服務(wù)器,有歸屬的區(qū)別
id_token 是一個(gè)jwt,里面帶有用戶的唯一標(biāo)識(shí),我們?cè)谂袛嘣撚脩粢呀?jīng)存在的時(shí)候不需要再請(qǐng)求userinfo endpoint
下圖是對(duì)id_token進(jìn)行解析得到的信息:sub即subject_id(用戶唯一標(biāo)識(shí) )
jwt了解的同學(xué)知道它里面本身就可以存儲(chǔ)用戶的信息,那么id_token可以嗎?答案當(dāng)然是可以的,我們將在介紹完identity server4的集成之后最后來(lái)實(shí)現(xiàn)。
Identity Server4提供的OIDC認(rèn)證服務(wù)
Identity Server4是asp.net core2.0實(shí)現(xiàn)的一套o(hù)Auth2 和OIDC框架,用它我們可以很快速的搭建一套自己的認(rèn)證和授權(quán)服務(wù)。我們來(lái)看一下用它如何快速實(shí)現(xiàn)OIDC認(rèn)證服務(wù)。
由于用戶登錄代碼過(guò)多,完整代碼可以加入ASP.NET Core QQ群 92436737獲取。 此處僅展示配置核心代碼。
過(guò)程
新建asp.net core web應(yīng)用程序
添加identityserver4 nuget引用
依賴注入初始化
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers());
中間件添加
配置
在測(cè)試的時(shí)候我們新建一個(gè)Config.cs來(lái)放一些配置信息
api resources
public static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>{
new ApiResource("api1", "API Application"){UserClaims = { "role", JwtClaimTypes.Role }}};}
identity resources
public static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource> {
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),};}
clients
我們要講的關(guān)鍵信息在這里,client有一個(gè)AllowGrantTypes它是一個(gè)string的集合。我們要寫進(jìn)去的值就是我們?cè)谏弦还?jié)講三種模式: Code,Implict和Hybird。因?yàn)檫@三種模式?jīng)Q定了我們的response_type可以請(qǐng)求哪幾個(gè)值,所以這個(gè)地方一定不能寫錯(cuò)。
IdentityServer4.Models.GrantTypes這個(gè)枚舉給我們提供了一些選項(xiàng),實(shí)際上是把oAuth的4種和OIDC的3種進(jìn)行了組保。
public static IEnumerable<Client> GetClients(){ return new List<Client>{ new Client{ClientId = "postman",AllowedGrantTypes = GrantTypes.Hybird,RedirectUris = { "https://localhost:5001/oauth2/callback" },ClientSecrets ={ new Secret("secret".Sha256())},AllowedScopes = new List<string>{IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile, "api1"},AllowOfflineAccess=true,},};}users
public static List<TestUser> GetTestUsers() {return new List<TestUser> {
new TestUser {SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",Username = "scott",Password = "password",Claims = new List<Claim> {
new Claim(JwtClaimTypes.Name, "scott"),
new Claim(JwtClaimTypes.FamilyName, "liu"),
new Claim(JwtClaimTypes.Email, "scott@scottbrady91.com"),
new Claim(JwtClaimTypes.Role, "user"),
}}};}
ASP.NET Core的權(quán)限體系中的OIDC認(rèn)證框架
在Microsoft.AspNetCore.All nuget引用中包含了Microsoft.AspNetCore.Authentication.OpenIdConnect即asp.net core OIDC的客戶端。我們需要在依賴注入中添加以下配置:
services.AddAuthentication(options =>{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{options.SignInScheme = "Cookies";options.Authority = "http://localhost:5000";options.RequireHttpsMetadata = false;options.ClientId = "postman";options.ClientSecret = "secret";options.ResponseType = "code id_token";options.GetClaimsFromUserInfoEndpoint = true;options.Scope.Add("api1");options.Scope.Add("offline_access");});Authority即我們的用identity server4搭建的認(rèn)證授權(quán)服務(wù)器,而其中的GetClaimsFromUserInfoEndpoint則會(huì)在拿到id_token之后自動(dòng)向userinfo endpoint請(qǐng)求用戶信息并放到asp.net core的User Identity下。
我們上面講過(guò),可以不需要請(qǐng)求userinfo endpoint, 直接將用戶信息放到id_token中。
樣我們就不需要再向userinfo endpoint發(fā)起請(qǐng)求,從id_token中即可以獲取到用戶的信息。而有了identity server4的幫助,完成這一步只需要一句簡(jiǎn)單的配置即可:
new Client { ClientId = "postman",AlwaysIncludeUserClaimsInIdToken = true,AllowOfflineAccess=true, }?這樣我們?cè)谀玫絠d_token之后,里即包含了我們的用戶信息。
相關(guān)文章:
學(xué)習(xí)Identity Server 4的預(yù)備知識(shí)
?使用Identity Server 4建立Authorization Server (1)
使用Identity Server 4建立Authorization Server (2)
使用Identity Server 4建立Authorization Server (3)
使用Identity Server 4建立Authorization Server (4)
使用Identity Server 4建立Authorization Server (5)
Identity Service - 解析微軟微服務(wù)架構(gòu)eShopOnContainers(二)
IdentityServer4 第三方快速入門和示例
IdentityServer4 SigningCredential(RSA 證書加密)
原文地址:https://github.com/jessetalk/aspnet-core-in-practise/blob/master/chapter1.md?
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的OIDC在 ASP.NET Core中的应用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 揽货最短路径解决方案算法 - C# 蚁群
- 下一篇: 【ASP.NET Core】处理异常