[原創(chuàng)] IdentityServer4權(quán)限控制---使用 ASP.NET Core 的交互式應(yīng)用程序(四)
寫了半天,不小心一關(guān)瀏覽器,沒了!我也是醉了。。。又重新寫一遍吧!
前面三節(jié)課我們學(xué)習(xí)了用一個(gè)客戶端先去申請(qǐng)令牌,得到令牌后再去訪問API資源這樣一個(gè)簡(jiǎn)單的流程,也是一個(gè)很常見的功能,通過前三節(jié)課的學(xué)習(xí),我們搭建了一臺(tái)API資源服務(wù)器,一臺(tái)IDS4SERVER身份認(rèn)證服務(wù)器,這節(jié)課我們接著上面的內(nèi)容繼續(xù)學(xué)習(xí)一下交互式登錄的流程。
開始之前,我先交待一下今天的學(xué)習(xí)任務(wù)吧!今天我們要用前面的IDS4SERVER服務(wù)器為我們新建的一個(gè)站點(diǎn)完成身份驗(yàn)證的過程,整個(gè)驗(yàn)證過程中有登錄交互,有登錄成功后的跳轉(zhuǎn),有登出功能,同樣,客戶拿到令牌后,對(duì)我們的網(wǎng)站擁有了“訪問受保護(hù)資源的功能”。本文的標(biāo)題后半部分是根據(jù)官方的文檔直譯的,想看原文的在這里,為了實(shí)現(xiàn)實(shí)驗(yàn)?zāi)康模覀冃枰龅墓ぷ饔?,?chuàng)建一個(gè)MYMVC的新站點(diǎn),該站點(diǎn)有首頁(yè)、WHOAMI、LOGOUT等頁(yè)面與功能,其中首頁(yè)和隱私說(shuō)明頁(yè)是模板自帶的,可以匿名瀏覽,而WHOAMI是登錄后才可訪問的,LOGOUT可以現(xiàn)在登出功能。除此之外,我們要做的工作還有搭建一臺(tái)IDS4SERVER服務(wù)器,為站點(diǎn)提供身份驗(yàn)證功能。OK,我們開始吧!
我們需要先搭建一臺(tái)IDS4SERVER服務(wù)器,不會(huì)的朋友們請(qǐng)參考這里:
[原創(chuàng)] IdentityServer4權(quán)限控制---客戶端授權(quán)模式之IDS4認(rèn)證服務(wù)器搭建(二)
懶一些的朋友也可以直接這里下載: IdentityServer4 ,注意,因?yàn)槲覀円黾右恍┙换スδ?,而第二?jié)課中的IDS4SERVER服務(wù)器并沒有交互頁(yè)面,所以我們還需要增加一個(gè)控制器和VIEW視圖界面,如果你是用我剛剛提到的鏈接搭建的服務(wù)器,則把這些代碼下下來(lái)放到項(xiàng)目中去,以給服務(wù)器添加控制器與視圖交互功能,如果用的是官方的,則跳過這一步。上面鏈接提供的代碼都是官方的DEMO,我只是改了一下命名空間名字。對(duì)了,代碼放進(jìn)去以后還要把控制器路由打開才行,不然代碼無(wú)法訪問,打開PROGRAM.CS文件,加入代碼
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"); 這樣控制器就可以使用了,我們?cè)囍\(yùn)行一下,并在瀏覽器中訪問一下試試。
OK,說(shuō)明我們的代碼可以正常運(yùn)行了。
緊接著我們?cè)傩陆ㄒ粋€(gè)MYMVC站點(diǎn)。
新建一個(gè)MYMVC的站點(diǎn)
指定存放目錄
然后選擇.NET6.0,HTTPS支持,其它默認(rèn),一路生成下來(lái),我們的新站點(diǎn)就OK了,我們?yōu)榱俗屨军c(diǎn)支持 OpenID Connect authentication協(xié)議,我們?cè)赑M命令行中運(yùn)行命令,引入包
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect打開新站點(diǎn)MYMVC的program.cs加入以下代碼:
using System.IdentityModel.Tokens.Jwt; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); JwtSecurityTokenHandler.DefaultMapInboundClaims = false; builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.SaveTokens = true; });
AddAuthentication
將認(rèn)證服務(wù)注入到依賴中去."Cookies"
為默認(rèn) DefaultScheme
, DefaultChallengeScheme
登錄質(zhì)詢方式改為oidc
指定 OpenID Connect 協(xié)議.然后用 AddCookie
添加 cookies處理程序, AddOpenIdConnect
配置 OpenID Connect 協(xié)議執(zhí)行. Authority
受信 token 地址。 ClientId
和 ClientSecret
. 用來(lái)標(biāo)識(shí)客戶。SaveTokens
用于將來(lái)自 IdentityServer 的令牌保存在 cookie 中(因?yàn)樯院髮⑿枰鼈儯?。再添加以下代碼,
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
讓以上設(shè)置生效。接下來(lái),我們?cè)俑脑煲幌挛覀兊男抡军c(diǎn),給它增加一些功能。打開HOMECONTROLLER,添加方法:
public IActionResult Logout() { return SignOut("Cookies", "oidc"); } [Authorize] public IActionResult Whoami() { return View(); }WHOAMI VIEW頁(yè)面
@using Microsoft.AspNetCore.Authentication <h2>Claims</h2> <dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl> <h2>Properties</h2> <dl> @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items) { <dt>@prop.Key</dt> <dd>@prop.Value</dd> } </dl>再改造一下模塊布局頁(yè),加上相應(yīng)的鏈接,運(yùn)行一下,如下圖
這時(shí)候我們不開身份認(rèn)證服務(wù)器,HOME\PRIVACY均可正常訪問,當(dāng)我們點(diǎn)擊WHOAMI頁(yè)面時(shí)報(bào)以下錯(cuò)誤:
An unhandled exception occurred while processing the request.
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.GetConfigurationAsync(CancellationToken cancel)
接下來(lái)我們對(duì)IDS4SERVER服務(wù)器做一些配置
添加對(duì) OpenID Connect 身份 scopes(范圍)的支持
與 OAuth 2.0 類似,OpenID Connect 也使用 scopes(范圍)概念。 同樣, scopes范圍代表您想要保護(hù)并且客戶想要訪問的東西。 與 OAuth 相比,OIDC 中的范圍不代表 API,而是代表用戶 ID、姓名或電子郵件地址等身份數(shù)據(jù)。
通過修改 Config.cs 中的 IdentityResources 屬性,添加對(duì)標(biāo)準(zhǔn) openid(subject id)和配置文件(first name, last name 等)范圍的支持:
打開config.cs (無(wú)語(yǔ)的輸入法...)
public static IEnumerable<IdentityResource> IdentityResources => new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), };然后打開PROGRAM.CS把上面的代碼在 IdentityServer 身份資源中注冊(cè)
builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddTestUsers(TestUsers.Users) .AddDeveloperSigningCredential();
注意,官網(wǎng)在這里漏寫了最下面一條,如果是測(cè)試用,會(huì)報(bào)錯(cuò),用我上面的代碼。標(biāo)準(zhǔn)scopes \claims 的申明,大家可以參考這個(gè)鏈接 :All standard scopes and their corresponding claims can be found in the OpenID Connect specification
添加測(cè)試用戶:
官方說(shuō):示例 UI 帶有一個(gè)內(nèi)存中的“用戶數(shù)據(jù)庫(kù)”。 您可以通過添加 AddTestUsers 擴(kuò)展方法在 IdentityServer 中啟用此功能:AddTestUsers(TestUsers.Users)
我們打開TestUsers.Users
可以看到有alice\bob兩個(gè)用戶。注意一下他們的一些NAME/FAMILYNAME等字段信息,后面會(huì)用到。
將客戶端標(biāo)識(shí)注冊(cè)IdentityServer 服務(wù)中:
最后一步是將 MVC 客戶端的新配置條目添加到 IdentityServer。
基于 OpenID Connect 的客戶端與我們目前添加的 OAuth 2.0 客戶端非常相似。 但由于 OIDC 中的流程始終是交互式的,因此我們需要在配置中添加一些重定向 URL。
public static IEnumerable<Client> Clients => new List<Client> { // machine to machine client (from quickstart 1) new Client { ClientId = "client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, // scopes that client has access to AllowedScopes = { "api1" } }, // interactive ASP.NET Core MVC client new Client { ClientId = "mvc", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } };OK,大功告成!不出意外的話,我們的服務(wù)器已經(jīng)搭建好了。我們把它運(yùn)行起來(lái),然后把我們新建的MYMVC站點(diǎn)也運(yùn)行起來(lái),再去點(diǎn)擊WHOAMI頁(yè)面試試,發(fā)現(xiàn)被導(dǎo)航到了認(rèn)證服務(wù)器登錄頁(yè)面,注意看后面的端口后5002變成5001了,這是兩個(gè)不同的站點(diǎn)。
我們用ALICE:ALICE(小寫,該死的輸入法?。┑卿?,成功以后又返回到我們的新站點(diǎn),注意看前后端口號(hào)的變化
這時(shí)候還要注意一個(gè)細(xì)節(jié),我讓大家在前面注意的那些NAME之類的字段并沒有成功返回。我們打開MYMVC的PROGRAM.CS,改動(dòng)代碼如下:
.AddOpenIdConnect("oidc", options => //AddOpenIdConnect is used to configure the handler that performs the OpenID Connect protocol.
{
options.Authority = "https://localhost:5001"; //The Authority indicates where the trusted token service is located.
options.ClientId = "mvc"; //We then identify this client via the ClientId and the ClientSecret
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true; //SaveTokens is used to persist the tokens from IdentityServer in the cookie (as they will be needed later).
// ... 讓服務(wù)器返回profile identity scop,如名稱,網(wǎng)站,家庭等
options.Scope.Add("profile");
options.GetClaimsFromUserInfoEndpoint = true;
// ...
});
然后登出重新請(qǐng)求試試,字段成功返回了。大家自己測(cè)試,我就不配圖了。
寫一個(gè)控制器Alice,只允許Alice一個(gè)人訪問,其他用戶拒絕,該如何實(shí)現(xiàn)呢?方法:注冊(cè)一個(gè)安全策略,
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Alice", policy => policy.RequireClaim("name", "Alice Smith"));
});
var app = builder.Build();
控制器
[Authorize(Policy = "Alice")]
public IActionResult Alice()
{
return View();
}
進(jìn)一步的實(shí)驗(yàn)
隨意向測(cè)試用戶添加更多聲明 - 以及更多身份資源。
定義身份資源的過程如下:
將新的身份資源添加到列表中 - 為其命名并指定在請(qǐng)求此資源時(shí)應(yīng)返回哪些聲明
通過客戶端配置上的 AllowedScopes 屬性授予客戶端對(duì)資源的訪問權(quán)限
通過將資源添加到客戶端中 OpenID Connect 處理程序配置上的 Scopes 集合來(lái)請(qǐng)求資源
(可選)如果身份資源與非標(biāo)準(zhǔn)聲明(例如 myclaim1)相關(guān)聯(lián),則在客戶端添加出現(xiàn)在 JSON 中的聲明(從 UserInfo 端點(diǎn)返回)和用戶聲明之間的 ClaimAction 映射
使用 Microsoft.AspNetCore.Authentication
// ...
.AddOpenIdConnect("oidc", options=>
{
// ...
options.ClaimActions.MapUniqueJsonKey("myclaim1", "myclaim1");
// ...
});
還值得注意的是,令牌聲明的檢索是一個(gè)可擴(kuò)展點(diǎn) - IProfileService。由于我們使用的是 AddTestUsers,因此默認(rèn)使用 TestUserProfileService。您可以在此處檢查源代碼以了解其工作原理。
原創(chuàng)文章,創(chuàng)作不易,轉(zhuǎn)載請(qǐng)注明出處:http://www.bemnnoss.com/article-30