ASP.NET Core的身份认证框架IdentityServer4--入门【转】
原文地址
Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,為ASP.NET Core和.NET Core進(jìn)行了更新和重新設(shè)計(jì)。在本文中,我們將快速了解IdentityServer 4存在的原因,然后直接進(jìn)入并創(chuàng)建一個(gè)從零到英雄的工作實(shí)現(xiàn)。
IdentityServer 3與IdentityServer 4
目前流行的一句話是“概念上兼容”,但這對(duì)于Identity Server 4來說是正確的。概念是相同的,它仍然是按照規(guī)范構(gòu)建的OpenID Connect提供程序,但是它的大部分內(nèi)部和可擴(kuò)展性點(diǎn)已經(jīng)改變。當(dāng)我們將客戶端應(yīng)用程序與IdentityServer集成時(shí),我們沒有集成到實(shí)現(xiàn)中。相反,我們使用OpenID Connect或OAuth規(guī)范進(jìn)行集成。這意味著當(dāng)前與IdentityServer 3一起使用的任何應(yīng)用程序都將與IdentityServer 4一起使用。
Identity Server被設(shè)計(jì)為作為自托管組件運(yùn)行,使用ASP.NET 4.x很難實(shí)現(xiàn),而MVC仍然與IIS和System.Web緊密耦合,從而導(dǎo)致katana提供內(nèi)部視圖引擎零件。借助在ASP.NET Core上運(yùn)行的Identity Server 4,我們現(xiàn)在可以在ASP.NET Core可以運(yùn)行的任何環(huán)境中使用任何UI技術(shù)和主機(jī)IdentityServer。這也意味著我們現(xiàn)在可以與現(xiàn)有的登錄表單/系統(tǒng)集成,從而實(shí)現(xiàn)升級(jí)。
IUserService用于集成用戶存儲(chǔ)?的Identity Server?現(xiàn)在也已消失,取而代之的是以IProfileService和形式的新用戶存儲(chǔ)抽象IResourceOwnerPasswordValidator。
IdentityServer 3不會(huì)去任何地方,就像.NET Framework不會(huì)去任何地方一樣。就像微軟已將大多數(shù)活動(dòng)開發(fā)轉(zhuǎn)移到.NET Core(參見Katana和ASP.NET Identity)一樣,我想IdentityServer最終會(huì)做同樣的事情,但我們?cè)谶@里討論的是OSS,而項(xiàng)目保持這種狀態(tài)它始終是開放的PRs修復(fù)錯(cuò)誤和相關(guān)的新功能。我不會(huì)很快放棄它,商業(yè)支持將繼續(xù)下去。
在寫作的初始階段,IdentityServer 4在RC5中,IdentityServer 3在v2.5.3上,計(jì)劃在未來使用另一個(gè)主要版本(v3.0.0)。此文章已更新為IdentityServer 4 v2.0。
IdentityServer4以.NET標(biāo)準(zhǔn)2.0為目標(biāo),這意味著它可以針對(duì).NET核心或.NET框架,盡管本文僅針對(duì).NET Core。IdentityServer 4現(xiàn)在支持.NET Core 2.0,由于兩個(gè)版本之間的重大變化而留下.NET Core 1.x。
您可以在Dominick Baier的IdentityServer 4公告文章中閱讀有關(guān)IdentityServer 4背后原因的更多信息。
從.NET Core 2.0開始,還有一些重大變化。對(duì)于ASP.NET Core 1.x支持,請(qǐng)查看主存儲(chǔ)庫中的aspnetcore1分支。
在ASP.NET Core和.NET Core上實(shí)現(xiàn)IdentityServer4
對(duì)于我們的初始實(shí)現(xiàn),我們將使用為演示和輕量級(jí)實(shí)現(xiàn)保留的內(nèi)存服務(wù)。在本文的后面,我們將切換到實(shí)體框架,以更真實(shí)地表示IdentityServer的生產(chǎn)實(shí)例。
在開始本教程之前,請(qǐng)確保您使用的是最新版本的ASP.NET Core和.NET Core工具。在創(chuàng)建本教程時(shí),我使用了.NET Core 2.0和Visual Studio 2017。
首先,我們需要一個(gè)使用.NET Core的新ASP.NET Core項(xiàng)目(在VS中參見'ASP.NET Core Web Application')。您將需要使用沒有身份驗(yàn)證的Empty模板。確保您的項(xiàng)目設(shè)置為.NET Core和ASP.NET Core 2.0。
在開始編碼之前,將項(xiàng)目URL切換為HTTPS。在沒有TLS的情況下,您不應(yīng)該運(yùn)行身份驗(yàn)證服務(wù)。假設(shè)您使用的是IIS Express,則可以通過打開項(xiàng)目屬性,進(jìn)入“調(diào)試”選項(xiàng)卡并單擊“啟用SSL”來執(zhí)行此操作。雖然我們?cè)谶@里,但您應(yīng)該將生成的HTTPS URL作為App URL,這樣當(dāng)我們運(yùn)行項(xiàng)目時(shí),我們就會(huì)從正確的頁面開始。
如果在為localhost使用IIS Express開發(fā)證書時(shí)遇到證書信任問題,請(qǐng)嘗試按照本文中的步驟操作。如果您發(fā)現(xiàn)此方法存在問題,請(qǐng)隨意切換到自托管模式(而不是IIS Express,使用項(xiàng)目的命名空間運(yùn)行)。
首先,我們需要安裝以下nuget包(目前為2.0.2編寫的文章):
IdentityServer4現(xiàn)在到我們的Startup類開始注冊(cè)依賴項(xiàng)和連接服務(wù)。
在您的ConfigureServices方法中添加以下內(nèi)容以注冊(cè)所需的最低依賴項(xiàng):
services.AddIdentityServer() .AddInMemoryClients(new List<Client>()) .AddInMemoryIdentityResources(new List<IdentityResource>()) .AddInMemoryApiResources(new List<ApiResource>()) .AddTestUsers(new List<TestUser>()) .AddDeveloperSigningCredential();然后在您的Configure方法中添加以下內(nèi)容以將IdentityServer中間件添加到HTTP管道:
app.UseIdentityServer();我們?cè)谶@里做的是在我們的DI容器中注冊(cè)IdentityServer?AddIdentityServer,使用演示簽名證書AddDeveloperSigningCredential,并為我們的客戶,資源和用戶使用內(nèi)存存儲(chǔ)。通過使用,AddIdentityServer我們還將所有生成的令牌/授權(quán)存儲(chǔ)在內(nèi)存中。我們將很快添加實(shí)際的客戶,資源和用戶。
UseIdentityServer?允許IdentityServer開始攔截路由并處理請(qǐng)求。
我們實(shí)際上可以運(yùn)行IdentityServer,它可能沒有UI,不支持任何范圍并且沒有用戶,但您已經(jīng)可以開始使用它了!查看OpenID Connect Discovery文檔/.well-known/openid-configuration。
OpenID Connect Discovery文檔
OpenID Connect Discovery文檔(被親切地稱為disco doc)可在此著名端點(diǎn)的每個(gè)OpenID Connect提供程序上使用(根據(jù)規(guī)范)。本文檔包含各種端點(diǎn)的位置(例如令牌端點(diǎn)和結(jié)束會(huì)話端點(diǎn)),提供程序支持的授權(quán)類型,可提供的范圍等信息。通過這個(gè)標(biāo)準(zhǔn)化文檔,我們開辟了自動(dòng)集成的可能性。
您可以在OpenID Connect Discovery 1.0規(guī)范中閱讀有關(guān)OpenID Connect Discovery文檔的更多信息。
簽署證書
簽名證書是用于簽署令牌的專用證書,允許客戶端應(yīng)用程序驗(yàn)證令牌的內(nèi)容在傳輸過程中未被更改。這涉及用于簽署令牌的私鑰和用于驗(yàn)證簽名的公鑰??蛻舳藨?yīng)用程序可以通過jwks_uriOpenID Connect發(fā)現(xiàn)文檔訪問此公鑰。
當(dāng)您創(chuàng)建并使用自己的簽名證書時(shí),請(qǐng)隨意使用自簽名證書。此證書不需要由受信任的證書頒發(fā)機(jī)構(gòu)頒發(fā)。
現(xiàn)在我們啟動(dòng)并運(yùn)行IdentityServer,讓我們添加一些數(shù)據(jù)。
客戶,資源和用戶
首先,我們需要存儲(chǔ)允許使用IdentityServer的客戶端應(yīng)用程序,以及這些客戶端可以使用的資源以及允許對(duì)其進(jìn)行身份驗(yàn)證的用戶。
我們目前正在使用InMemory商店,這些商店接受他們各自實(shí)體的集合,我們現(xiàn)在可以使用一些靜態(tài)方法填充它們。
客戶端
IdentityServer需要知道允許哪些客戶端應(yīng)用程序使用它。我想將此視為白名單,即您的訪問控制列表。然后將每個(gè)客戶端應(yīng)用程序配置為僅允許執(zhí)行某些操作,例如,他們只能請(qǐng)求將令牌返回到某些URL,或者他們只能請(qǐng)求某些信息。他們有訪問范圍。
internal class Clients { public static IEnumerable<Client> Get() { return new List<Client> { new Client { ClientId = "oauthClient", ClientName = "Example Client Credentials Client Application", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = new List<Secret> { new Secret("superSecretPassword".Sha256())}, AllowedScopes = new List<string> {"customAPI.read"} } }; } }這里我們添加一個(gè)使用Client Credentials OAuth授權(quán)類型的客戶端。此授權(quán)類型需要客戶端ID和客戶端密鑰來授權(quán)訪問,使用Identity Server提供的擴(kuò)展方法簡單地對(duì)密碼進(jìn)行哈希處理(畢竟我們從不在純文本中存儲(chǔ)任何密碼,這總比沒有好)。允許的范圍是允許此客戶端請(qǐng)求的范圍列表。這里我們的范圍是customAPI.read,我們現(xiàn)在將以API資源的形式初始化它。
資源和范圍
范圍代表您可以做的事情。它們代表我之前提到的范圍訪問。在IdentityServer 4中,作用域被建模為資源,它有兩種形式:Identity和API。標(biāo)識(shí)資源允許您為將返回特定聲明集的作用域建模,而API資源作用域允許您建模對(duì)受保護(hù)資源(通常是API)的訪問。
internal class Resources { public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResource { Name = "role", UserClaims = new List<string> {"role"} } }; } public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource { Name = "customAPI", DisplayName = "Custom API", Description = "Custom API Access", UserClaims = new List<string> {"role"}, ApiSecrets = new List<Secret> {new Secret("scopeSecret".Sha256())}, Scopes = new List<Scope> { new Scope("customAPI.read"), new Scope("customAPI.write") } } }; } }IdentityResources
前三個(gè)身份資源代表我們希望IdentityServer支持的一些標(biāo)準(zhǔn)OpenID Connect定義的范圍。例如,email范圍允許返回email和email_verified聲明。我們還創(chuàng)建了一個(gè)自定義標(biāo)識(shí)資源,其形式為經(jīng)過身份驗(yàn)證的用戶role返回role聲明。
快速提示,openid使用OpenID Connect流時(shí)始終需要范圍。您可以在OpenID Connect規(guī)范中找到有關(guān)這些的更多信息。
ApiResources
對(duì)于api資源,我們正在建模一個(gè)我們希望保護(hù)的API?customApi。此API有兩個(gè)可以請(qǐng)求的范圍:customAPI.read和customAPI.write。
通過在這樣的范圍內(nèi)設(shè)置聲明,我們確保將這些聲明類型添加到具有此范圍的任何標(biāo)記中(當(dāng)然,如果用戶具有該類型的值)。在這種情況下,我們確保將用戶角色聲明添加到具有此范圍的任何令牌。稍后將在令牌自省期間使用范圍秘密。
范圍與資源
OpenID Connect和OAuth作用域現(xiàn)在被建模為資源,是IdentityServer 3和IdentityServer 4之間最大的概念上的變化。
offline_access現(xiàn)在,默認(rèn)情況下支持用于請(qǐng)求刷新令牌?的作用域,并授權(quán)使用由該Client屬性控制的此作用域AllowOfflineAccess。
用戶
在完全成熟的用戶存儲(chǔ)(如ASP.NET Identity)的位置,我們可以使用TestUsers:
internal class Users { public static List<TestUser> Get() { return new List<TestUser> { new TestUser { SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE", Username = "scott", Password = "password", Claims = new List<Claim> { new Claim(JwtClaimTypes.Email, "scott@scottbrady91.com"), new Claim(JwtClaimTypes.Role, "admin") } } }; } }用戶主題(或子)聲明是其唯一標(biāo)識(shí)符。這應(yīng)該是您的身份提供商獨(dú)有的東西,而不是電子郵件地址。我指出這是由于最近Azure AD的漏洞。
我們現(xiàn)在需要使用此信息更新我們的DI容器(而不是以前的空集合):
services.AddIdentityServer() .AddInMemoryClients(Clients.Get()) .AddInMemoryIdentityResources(Resources.GetIdentityResources()) .AddInMemoryApiResources(Resources.GetApiResources()) .AddTestUsers(Users.Get()) .AddDeveloperSigningCredential();如果您再次運(yùn)行此命令并再次訪問發(fā)現(xiàn)文檔,您現(xiàn)在將看到填充的部分scopes_supported和claims_supported部分。
OAuth功能
為了測(cè)試我們的實(shí)現(xiàn),我們可以使用之前的OAuth客戶端從Identity Server獲取訪問令牌。這將使用Client Credentials流程,因此我們的請(qǐng)求將如下所示:
POST /connect/token Headers: Content-Type: application/x-www-form-urlencoded Body: grant_type=client_credentials&scope=customAPI.read&client_id=oauthClient&client_secret=superSecretPassword這會(huì)將我們的訪問令牌作為JWT返回:
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE0M2U4MjljMmI1NzQ4OTk2OTc1M2JhNGY4MjA1OTc5ZGYwZGE5ODhjNjQwY2ZmYTVmMWY0ZWRhMWI2ZTZhYTQiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE0ODE0NTE5MDMsImV4cCI6MTQ4MTQ1NTUwMywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTAiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTAvcmVzb3VyY2VzIiwiY3VzdG9tQVBJIl0sImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50Iiwic2NvcGUiOlsiY3VzdG9tQVBJLnJlYWQiXX0.D50LeW9265IH695FlykBiWVkKDj-Gjiv-8q-YJl9qV3_jLkTFVeHUaCDuPfe1vd_XVxmx_CwIwmIGPXftKtEcjMiA5WvFB1ToafQ1AqUzRyDgugekWh-i8ODyZRped4SxrlI8OEMcbtTJNzhfDpyeYBiQh7HeQ6URn4eeHq3ePqbJSTPrqsYyG9YpU6azO7XJlNeq_Ml1KZms1lxrkXcETfo7U1h-z66TxpvH4qQRrRcNOY_kejq1x_GD3peWcoKPJ_f4Rbc4B-UvqicslKM44dLNoMDVw_gjKHRCUaaevFlzyS59pwv0UHFAuy4_wyp1uX7ciQOjUPyhl63ZEOX1w", "expires_in": 3600, "token_type": "Bearer"如果我們將此訪問令牌轉(zhuǎn)到j(luò)wt.io,我們可以看到它包含以下聲明:
"alg": "RS256", "kid": "143e829c2b57489969753ba4f8205979df0da988c640cffa5f1f4eda1b6e6aa4", "typ": "JWT" "nbf": 1481451903, "exp": 1481455503, "iss": "https://localhost:44350", "aud": [ "https://localhost:44350/resources", "customAPI" ], "client_id": "oauthClient", "scope": [ "customAPI.read" ]我們現(xiàn)在可以使用IdentityServer的令牌內(nèi)省端點(diǎn)來驗(yàn)證令牌,就好像我們是從外部方接收它的OAuth資源一樣。如果成功,我們將收到該標(biāo)記中的聲明回復(fù)給我們。請(qǐng)注意,IdentityServer 4中的訪問令牌驗(yàn)證端點(diǎn)在IdentityServer 4中不再可用。
在這里,我們之前創(chuàng)建的范圍秘密通過使用基本身份驗(yàn)證來使用,其中用戶名是范圍Id,密碼是范圍秘密。
POST /connect/introspect Headers: Authorization: Basic Y3VzdG9tQVBJOnNjb3BlU2VjcmV0 Content-Type: application/x-www-form-urlencoded Body: token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjE0M2U4MjljMmI1NzQ4OTk2OTc1M2JhNGY4MjA1OTc5ZGYwZGE5ODhjNjQwY2ZmYTVmMWY0ZWRhMWI2ZTZhYTQiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE0ODE0NTE5MDMsImV4cCI6MTQ4MTQ1NTUwMywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTAiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTAvcmVzb3VyY2VzIiwiY3VzdG9tQVBJIl0sImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50Iiwic2NvcGUiOlsiY3VzdG9tQVBJLnJlYWQiXX0.D50LeW9265IH695FlykBiWVkKDj-Gjiv-8q-YJl9qV3_jLkTFVeHUaCDuPfe1vd_XVxmx_CwIwmIGPXftKtEcjMiA5WvFB1ToafQ1AqUzRyDgugekWh-i8ODyZRped4SxrlI8OEMcbtTJNzhfDpyeYBiQh7HeQ6URn4eeHq3ePqbJSTPrqsYyG9YpU6azO7XJlNeq_Ml1KZms1lxrkXcETfo7U1h-z66TxpvH4qQRrRcNOY_kejq1x_GD3peWcoKPJ_f4Rbc4B-UvqicslKM44dLNoMDVw_gjKHRCUaaevFlzyS59pwv0UHFAuy4_wyp1uX7ciQOjUPyhl63ZEOX1w響應(yīng):
"nbf": 1481451903, "exp": 1481455503, "iss": "https://localhost:44350", "aud": [ "https://localhost:44350/resources", "customAPI" ], "client_id": "oauthClient", "scope": [ "customAPI.read" ], "active": true如果您希望以編程方式執(zhí)行此過程并以此方式授權(quán)訪問.NET Core資源,請(qǐng)查看IdentityServer4.AcessTokenValidation庫。
資源所有者密碼憑據(jù)(ROPC)授予類型
IdentityServer文檔還提供了有關(guān)如何使用資源所有者授權(quán)類型的指南。不要被這種授權(quán)類型包含用戶名和密碼的事實(shí)所迷惑,它仍然只是授權(quán)而不是身份驗(yàn)證。實(shí)際上,文章和原始OAuth 2.0規(guī)范中有多個(gè)免責(zé)聲明,聲明此授權(quán)類型應(yīng)僅用于舊版應(yīng)用程序。請(qǐng)參閱我的文章為什么資源所有者密碼憑據(jù)授予類型不是身份驗(yàn)證也不適合現(xiàn)代應(yīng)用程序,以調(diào)查資源所有者授予類型的所有錯(cuò)誤。
用戶界面
到目前為止,我們一直在沒有UI工作,讓我們通過從使用ASP.NET Core MVC的GitHub引入Quickstart UI來改變這一點(diǎn)。
要下載此文件,請(qǐng)將repo中的所有文件夾復(fù)制到項(xiàng)目中,或使用以下powershell命令(同樣,在項(xiàng)目文件夾中):
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))現(xiàn)在我們需要將ASP.NET MVC Core添加到我們的項(xiàng)目中。為此,首先將以下包添加到項(xiàng)目中(如果已安裝,則可以跳過此安裝Microsoft.AspNetCore.All):
Microsoft.AspNetCore.Mvc Microsoft.AspNetCore.StaticFiles然后添加到您的服務(wù)(ConfigureServices):
services.AddMvc();最后添加到HTTP管道的末尾(Configure):
app.UseStaticFiles(); app.UseMvcWithDefaultRoute();現(xiàn)在,當(dāng)我們運(yùn)行項(xiàng)目時(shí),我們會(huì)看到一個(gè)閃屏。萬歲!現(xiàn)在我們有了UI,現(xiàn)在我們可以開始驗(yàn)證用戶了。
IdentityServer 4快速入門UI啟動(dòng)畫面
OpenID Connect
要使用OpenID Connect演示身份驗(yàn)證,我們需要?jiǎng)?chuàng)建一個(gè)客戶端Web應(yīng)用程序并在IdentityServer中添加相應(yīng)的客戶端。
首先,我們需要在IdentityServer中添加一個(gè)新客戶端:
new Client {ClientId = "openIdConnectClient", ClientName = "Example Implicit Client Application", AllowedGrantTypes = GrantTypes.Implicit, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "role", "customAPI.write" }, RedirectUris = new List<string> {"https://localhost:44330/signin-oidc"}, PostLogoutRedirectUris = new List<string> {"https://localhost:44330"} }重定向和后注銷重定向uris的位置是我們即將推出的應(yīng)用程序的URL。重定向uri需要路徑/signin-oidc,這條路徑將由即將推出的中間件自動(dòng)創(chuàng)建和處理。
這里我們使用OpenID Connect隱式授權(quán)類型。此授權(quán)類型允許我們通過瀏覽器請(qǐng)求身份和訪問令牌。我會(huì)稱之為最簡單的授權(quán)類型,但也是最不安全的。
客戶申請(qǐng)
現(xiàn)在我們需要?jiǎng)?chuàng)建客戶端應(yīng)用程序。為此,我們需要另一個(gè)ASP.NET Core網(wǎng)站,這次使用Web應(yīng)用程序(MVC)VS模板,但沒有認(rèn)證。
要將OpenID Connect身份驗(yàn)證添加到ASP.NET Core站點(diǎn),我們需要將以下兩個(gè)包添加到我們的站點(diǎn)(同樣,如果您使用,可以跳過安裝Microsoft.AspNetCore.All):
Microsoft.AspNetCore.Authentication.Cookies Microsoft.AspNetCore.Authentication.OpenIdConnect然后在我們的DI(ConfigureServices)中:
services.AddAuthentication(options => { options.DefaultScheme = "cookie"; }) .AddCookie("cookie");在這里,我們告訴我們的應(yīng)用程序使用cookie身份驗(yàn)證,登錄用戶,并將其用作默認(rèn)的身份驗(yàn)證方法。雖然我們可能正在使用IdentityServer對(duì)用戶進(jìn)行身份驗(yàn)證,但每個(gè)客戶端應(yīng)用程序仍需要發(fā)布自己的cookie(到其自己的域)。
現(xiàn)在我們需要添加OpenID Connect身份驗(yàn)證:
services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("cookie") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:44350/"; options.ClientId = "openIdConnectClient"; options.SignInScheme = "cookie"; });在這里,我們告訴我們的應(yīng)用程序使用我們的OpenID Connect Provider(IdentityServer),我們希望登錄的客戶端ID以及成功驗(yàn)證時(shí)登錄的身份驗(yàn)證類型(我們之前定義的cookie中間件)。
默認(rèn)情況下,ID連接中間件選項(xiàng)將使用/signin-oidc其重定向URI,請(qǐng)求范圍openid和profile,并用implicit流動(dòng)(只要求身份令牌)。
接下來我們需要在我們的管道(Configure)之前添加身份驗(yàn)證UseMvc:
app.UseAuthentication();現(xiàn)在剩下的就是讓頁面需要身份驗(yàn)證才能訪問。讓我們將“添加”屬性添加到“聯(lián)系人”操作,因?yàn)槁?lián)系我們的人是我們想要的最后一件事。
[Authorize] public IActionResult Contact() { ... }現(xiàn)在,當(dāng)我們運(yùn)行此應(yīng)用程序并選擇“聯(lián)系”頁面時(shí),我們將收到未經(jīng)授權(quán)的401。這反過來將被我們的OpenID Connect中間件攔截,該中間件將302重定向到我們的Identity Server身份驗(yàn)證端點(diǎn)以及必要的參數(shù)。
IdentityServer 4快速入門UI登錄屏幕
成功登錄后,IdentityServer將要求我們同意客戶端應(yīng)用程序代表您訪問某些信息或資源(這些信息或資源對(duì)應(yīng)于客戶端請(qǐng)求的身份和資源范圍)。可以在客戶端基于客戶端禁用此同意請(qǐng)求。默認(rèn)情況下,ASP.NET Core的OpenID Connect中間件將請(qǐng)求openid和配置文件范圍。
IdentityServer 4快速入門UI同意屏幕
這就是使用隱式授權(quán)類型連接簡單OpenID Connect Client所需的全部內(nèi)容。
Entity Framework Core
目前我們?cè)趦?nèi)存存儲(chǔ)中使用,正如我們之前提到的那樣,它是用于演示目的,或者最多是非常輕量級(jí)的實(shí)現(xiàn)。理想情況下,我們希望將各種商店移動(dòng)到一個(gè)持久性數(shù)據(jù)庫中,該數(shù)據(jù)庫在每次部署時(shí)都不會(huì)被刪除,或者需要更改代碼才能添加新條目。
IdentityServer有一個(gè)Entity Framework(EF)Cor??e包,我們可以使用它來使用任何EF Core關(guān)系數(shù)據(jù)庫提供程序?qū)崿F(xiàn)客戶端,范圍和持久授權(quán)存儲(chǔ)。
Identity Server Entity Framework Core軟件包已使用In-Memory,SQLite(內(nèi)存中)和SQL Server數(shù)據(jù)庫提供程序進(jìn)行了集成測(cè)試。如果您發(fā)現(xiàn)其他提供商存在任何問題或希望針對(duì)其他數(shù)據(jù)庫提供商編寫測(cè)試,請(qǐng)隨時(shí)在GitHub問題跟蹤器上打開問題或提交拉取請(qǐng)求)。
對(duì)于本文,我們將使用SQL服務(wù)器(SQL Express或本地?cái)?shù)據(jù)庫會(huì)這樣做),因此我們需要以下nuget包:
IdentityServer4.EntityFramework Microsoft.EntityFrameworkCore.SqlServer持久的贈(zèng)款商店
持久授權(quán)存儲(chǔ)包含有關(guān)給定同意的所有信息(因此我們不會(huì)一直要求對(duì)每個(gè)請(qǐng)求的同意),引用令牌(存儲(chǔ)的jwt,其中只有與jwt相對(duì)應(yīng)的密鑰被提供給請(qǐng)求者,使其易于撤銷),以及更多。如果沒有持久性存儲(chǔ),則在每次重新部署IdentityServer時(shí),令牌都將失效,并且我們無法一次承載多個(gè)安裝(無負(fù)載平衡)。
首先讓新的幾個(gè)變量:
const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=Test.IdentityServer4.EntityFramework;trusted_connection=yes;"; var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;然后,我們可以通過添加到AddIdentityServer以下內(nèi)容來添加對(duì)持久授權(quán)存儲(chǔ)的支持:
AddOperationalStore(options => options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))我們的遷移程序集是我們托管IdentityServer的項(xiàng)目。這對(duì)于不在您的托管項(xiàng)目中的DbContexts(在這種情況下它位于nuget包中)是必要的,并允許我們運(yùn)行EF遷移。否則,我們將遇到一個(gè)例外情況,例如:
Your target project 'Project.Host' doesn't match your migrations assembly 'Project.BusinessLogic'. Either change your target project or change your migrations assembly. Change your migrations assembly by using DbContextOptionsBuilder. E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("Project.Host")). By default, the migrations assembly is the assembly containing the DbContext. Change your target project to the migrations project by using the Package Manager Console's Default project drop-down list, or by executing "dotnet ef" from the directory containing the migrations project.客戶端和Scope存儲(chǔ)
要為我們需要類似的東西,我們的更換范圍和客戶商店添加持久存儲(chǔ)AddInMemoryClients,AddInMemoryIdentityResources并AddInMemoryApiResources用:
.AddConfigurationStore(options => options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))這些注冊(cè)還包括從我們的客戶端表中讀取的CORS策略服務(wù)。
運(yùn)行EF遷移
要運(yùn)行EF遷移,我們需要Microsoft.EntityFrameworkCore.Tools在csproj中將包作為CLI工具添加:
<ItemGroup><DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup>然后我們可以使用以下方法創(chuàng)建遷移:
dotnet ef migrations add InitialIdentityServerMigration -c PersistedGrantDbContext dotnet ef migrations add InitialIdentityServerMigration -c ConfigurationDbContext要使用我們之前使用的配置以編程方式創(chuàng)建客戶端和資源,請(qǐng)查看本文庫中的InitializeDbTestData方法。
ASP.NET Core Identity
為了為我們的用戶添加持久性存儲(chǔ),Identity Server 4提供了ASP.NET Core Identity (ASP.NET Identity 3)庫的集成。我們將使用ASP.NET核心身份實(shí)體框架庫和基礎(chǔ)IdentityUser實(shí)體再次使用SQL服務(wù)器執(zhí)行此操作:
IdentityServer4.AspNetIdentity Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer目前我們需要?jiǎng)?chuàng)建自己的自定義實(shí)現(xiàn),IdentityDbContext以覆蓋構(gòu)造函數(shù)以獲取非泛型版本DbContextOptions。這是因?yàn)镮dentityDbContext只有一個(gè)接受通用的構(gòu)造函數(shù)DbContextOptions,當(dāng)我們注冊(cè)多個(gè)DbContexts時(shí),會(huì)導(dǎo)致無效的操作異常。我已經(jīng)就此問題提出了一個(gè)問題,希望我們能盡快跳過這一步。
public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }然后,我們需要為我們的ConfigureServices方法添加ASP.NET Identity DbContext的注冊(cè)。
services.AddDbContext<ApplicationDbContext>(builder => builder.UseSqlServer(connectionString, sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly))); services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>();然后在我們的IdentityServerBuilder替換AddTestUsers中:
.AddAspNetIdentity<IdentityUser>()我們?cè)俅涡枰\(yùn)行遷移。這可以通過以下方式完成:
dotnet ef migrations add InitialIdentityServerMigration -c ApplicationDbContext這就是將ASP.NET核心身份與IdentityServer 4連接起來所需的全部內(nèi)容,但不幸的是,我們之前下載的Quickstart用戶界面不再正常工作,因?yàn)樗栽谑褂肨estUserStore。
但是,我們可以通過替換一些代碼,從Quickstart UI修改我們現(xiàn)有的AccountsController以適用于ASP.NET Core Identity。
首先,我們需要更改構(gòu)造函數(shù)以接受ASP.NET核心標(biāo)識(shí)UserManager,而不是現(xiàn)有的TestUserStore。我們的構(gòu)造函數(shù)現(xiàn)在應(yīng)該如下所示:
private readonly UserManager<IdentityUser> _userManager; private readonly IIdentityServerInteractionService _interaction; private readonly IEventService _events; private readonly AccountService _account; public AccountController( IIdentityServerInteractionService interaction, IClientStore clientStore, IHttpContextAccessor httpContextAccessor, IEventService events, UserManager<IdentityUser> userManager) { _userManager = userManager; _interaction = interaction; _events = events; _account = new AccountService(interaction, httpContextAccessor, clientStore); }通過刪除TestUserStore我們沒有破兩種方法:(?Login發(fā)布)和ExternalCallback。我們可以Login完全用以下方法替換該方法:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model, string button) { if (button != "login") { var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (context != null) { await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); return Redirect(model.ReturnUrl); } else { return Redirect("~/"); } } if (ModelState.IsValid) { var user = await _userManager.FindByNameAsync(model.Username); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { await _events.RaiseAsync( new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName)); AuthenticationProperties props = null; if (AccountOptions.AllowRememberLogin && model.RememberLogin) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; }; await HttpContext.SignInAsync(user.Id, user.UserName, props); if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } return Redirect("~/"); } await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); } var vm = await _account.BuildLoginViewModelAsync(model); return View(vm); }使用ExternalCallback回調(diào)方法,我們需要使用以下內(nèi)容替換find和provision邏輯:
[HttpGet] public async Task<IActionResult> ExternalLoginCallback() { var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); if (result?.Succeeded != true) { throw new Exception("External authentication error"); } var externalUser = result.Principal; var claims = externalUser.Claims.ToList(); var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject); if (userIdClaim == null) { userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier); } if (userIdClaim == null) { throw new Exception("Unknown userid"); } claims.Remove(userIdClaim); var provider = result.Properties.Items["scheme"]; var userId = userIdClaim.Value; var user = await _userManager.FindByLoginAsync(provider, userId); if (user == null) { user = new IdentityUser { UserName = Guid.NewGuid().ToString() }; await _userManager.CreateAsync(user); await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, userId, provider)); } var additionalClaims = new List<Claim>(); var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); if (sid != null) { additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); } AuthenticationProperties props = null; var id_token = result.Properties.GetTokenValue("id_token"); if (id_token != null) { props = new AuthenticationProperties(); props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); } await _events.RaiseAsync轉(zhuǎn)載于:https://www.cnblogs.com/miskis/p/9456420.html
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core的身份认证框架IdentityServer4--入门【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js打开新窗口的方法
- 下一篇: ASP.NET获取路径的方法