詳解IdentityServer4介紹和使用
一、概述
前幾篇文章介紹到,OWIN提供了一些OAuth2.0認(rèn)證的機(jī)制,使用OWIN可以很方便的實現(xiàn)OAuth2.0認(rèn)證和授權(quán)。但是在.NETCORE中更傾向于使用Identityserver4組件來構(gòu)建認(rèn)證授權(quán)服務(wù),原因是IdentityServer4 是為ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 認(rèn)證框架。 具體的可以看下IDS4的官方文檔。本文重點介紹IDS4實際使用過程中涉及到的技術(shù)點。下面先簡單介紹下IDS4中涉及到的概念及簡單使用流程。
OpenID是Authentication,即認(rèn)證,對用戶的身份進(jìn)行認(rèn)證。
OAuth是一個開放標(biāo)準(zhǔn),是Authorization,即授權(quán),允許用戶授權(quán)第三方移動應(yīng)用訪問他們存儲在其他服務(wù)商上存儲的私密的資源(如照片,視頻,聯(lián)系人列表),而無需將用戶名和密碼提供給第三方應(yīng)用。OAuth允許用戶提供一個令牌而不是用戶名和密碼來訪問他們存放在特定服務(wù)商上的數(shù)據(jù)。每一個令牌授權(quán)一個特定的網(wǎng)站內(nèi)訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth可以允許用戶授權(quán)第三方網(wǎng)站訪問他們存儲在另外服務(wù)提供者的某些特定信息,而非所有內(nèi)容。
OIDC是OpenID Connect的簡稱,是一個基于OAuth2協(xié)議的身份認(rèn)證標(biāo)準(zhǔn)協(xié)議。是認(rèn)證和授權(quán)的結(jié)合。OAuth2是一個授權(quán)協(xié)議,它無法提供完善的身份認(rèn)證功能,OIDC使用OAuth2的授權(quán)服務(wù)器來為第三方客戶端提供用戶的身份認(rèn)證,并把對應(yīng)的身份認(rèn)證信息傳遞給客戶端,且可以適用于各種類型的客戶端(比如服務(wù)端應(yīng)用,移動APP,JS應(yīng)用),且完全兼容OAuth2,也就是說你搭建了一個OIDC的服務(wù)后,也可以當(dāng)作一個OAuth2的服務(wù)來用。
1、OpenID認(rèn)證用戶的流程
- 用戶訪問xxx.com(該網(wǎng)站支持OpenID)
- xxx.com將用戶導(dǎo)向OpenID服務(wù)的登錄頁面
- 用戶輸入用戶名密碼,成功后回調(diào)到xxx.com網(wǎng)站,并攜帶用戶在OpenID服務(wù)中的唯一標(biāo)識(這個表示可能僅僅是一個GUID,不含用戶個人信息)
- xxx.com校檢成功后,就認(rèn)為用戶完成了登錄認(rèn)證
OpenID 目的就是做認(rèn)證,使用簡單,不透露用戶的個人信息。
2、OAuth認(rèn)證用戶的流程
OAuth是用來做授權(quán)的,如果用來做認(rèn)證,具體的流程如下圖所示:
- 用戶使用QQ登錄HelloFont
- HelloFont將用戶導(dǎo)向QQ授權(quán)服務(wù)的登錄頁面
- 用戶登錄成功并授權(quán)后,頁面返回HelloFont并攜帶訪問令牌
- 如果想獲取用戶的詳細(xì)信息,還需要通過訪問令牌再次調(diào)用QQ服務(wù)提供的相關(guān)接口進(jìn)行請求
可以看出,OAuth 相對于 OpenID 最大的區(qū)別就是,網(wǎng)站實際上是拿到了用戶帳戶訪問權(quán)限繼而確認(rèn)你的身份。同時OAuth還比OpenID多了幾個操作步驟。
3、IdentityServer4對象
下面簡單的介紹下Identityserver4中涉及的對象,具體的可以參考下官方文檔。
- 用戶(User):用戶是使用已注冊的客戶端(指在id4中已經(jīng)注冊)訪問資源的人。
- 客戶端(Client):客戶端就是從identityserver請求令牌的軟件(你可以理解為一個app即可),既可以通過身份認(rèn)證令牌來驗證識別用戶身份,又可以通過授權(quán)令牌來訪問服務(wù)端的資源。但是客戶端首先必須在申請令牌前已經(jīng)在identityserver服務(wù)中注冊過。
- 資源(Resources):資源就是你想用identityserver保護(hù)的東東,可以是用戶的身份數(shù)據(jù)或者api資源。
- 身份令牌(顧名思義用于做身份認(rèn)證,例如sso其實主要就是用于身份認(rèn)證):一個身份令牌指的就是對認(rèn)證過程的描述。它至少要標(biāo)識某個用戶(Called the sub aka subject claim)的主身份信息,和該用戶的認(rèn)證時間和認(rèn)證方式。但是身份令牌可以包含額外的身份數(shù)據(jù),具體開發(fā)者可以自行設(shè)定,但是一般情況為了確保數(shù)據(jù)傳輸?shù)男剩_發(fā)者一般不做過多額外的設(shè)置,大家也可以根據(jù)使用場景自行決定。
- 訪問令牌(用于做客戶端訪問授權(quán)):訪問令牌允許客戶端訪問某個 API 資源。客戶端請求到訪問令牌,然后使用這個令牌來訪問 API資源。訪問令牌包含了客戶端和用戶(如果有的話,這取決于業(yè)務(wù)是否需要,但通常不必要)的相關(guān)信息,API通過這些令牌信息來授予客戶端的數(shù)據(jù)訪問權(quán)限。
二、IdentityServer4實踐
1、構(gòu)建非持久化認(rèn)證服務(wù)項目
下面簡單的介紹下大體流程,看懂思路即可:
IDS4本身已經(jīng)將OAuth2.0+OIDC+SSO思想給實現(xiàn)了,并且提供了成熟的組件IdentityServer4,如下圖,只需要將該組件引入,進(jìn)行相關(guān)的配置即可。
正常來說我們通過nuget下載了IdentityServer4包,就需要在startup.cs中引入使用,如下:
using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace IdentityServer { public class Startup { public IHostingEnvironment Environment { get; } public Startup(IHostingEnvironment environment) { Environment = environment; } public void ConfigureServices(IServiceCollection services) { var builder = services.AddIdentityServer() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApis()) .AddInMemoryClients(Config.GetClients()); if (Environment.IsDevelopment()) { builder.AddDeveloperSigningCredential(); } else { throw new Exception("need to configure key material"); } } public void Configure(IApplicationBuilder app) { if (Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); } } }
可以從上面代碼看出,采用的是本地配置文件的方式,我們看下配置文件:
using IdentityServer4.Models; using System.Collections.Generic; namespace IdentityServer { public static class Config { public static IEnumerable<IdentityResource> GetIdentityResources() { return new IdentityResource[] { new IdentityResources.OpenId() }; } public static IEnumerable<ApiResource> GetApis() { return new List<ApiResource> { new ApiResource("api1", "My API") }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "api1" } } }; } } }
在配置文件中我們定義了身份資源IdentityResource、API資源Apis、客戶端Clients等等吧,具體怎么配置建議還是看一下官方文檔,很詳細(xì),里面包含了相應(yīng)的屬性及示例等,可根據(jù)實際需求進(jìn)行選擇配置。
簡單的認(rèn)證服務(wù)就搭建好了,當(dāng)然這個是比較簡單的,支持客戶端模式,不需要用戶參與的授權(quán)。如果說站外應(yīng)用需要使用授權(quán)碼模式、或者implact模式,我們搭建的identityserver4項目還要提供登錄授權(quán)等相關(guān)的頁面的,不然用戶在哪里登錄和授權(quán)呢。這個官網(wǎng)也有示例,可以在github中搜索下載源碼查看,直接使用他們提供的界面(mvc)即可,當(dāng)然也可以自己進(jìn)行UI優(yōu)化,但是他們提供的action的名稱最好不要更改,因為identityserver4包中退出、登錄相關(guān)的跳轉(zhuǎn)都是指定好的,通過示例來說明下為啥不建議改動:
截圖中是identityserver4提供的界面代碼(mvc),有個Account控制器,里面有退出登錄、授權(quán)受限等action,如果站外應(yīng)用使用授權(quán)碼模式登錄,發(fā)現(xiàn)授權(quán)受限或者用戶退出登錄,那么identityserver4服務(wù)會將用戶指向Account/Logout或者Account/AccessDenied,如果把名稱改了就找不到相應(yīng)的action了。當(dāng)然并不是所有的都不能改,比方說登錄,我就自己單獨在另一個action寫的(有需求的原因),所以需要在startup.cs引入identityserver4的時候指定,如下:
//用戶交互的選項 options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions { LoginUrl = "/login/index",//登錄地址 };
還有一種辦法就是將identityserver4源碼進(jìn)行二次開發(fā),改成你想要的樣子。。。
2、構(gòu)建持久化認(rèn)證服務(wù)項目
上邊的identityserver4的配置信息是寫死在文件中的,在實際開發(fā)中,還是要將配置信息寫入到數(shù)據(jù)庫中,所以就需要持久化了。另外還需要提供人為配置信息的管理界面??偟膩碚f就是基于IdentityServer4包加兩塊功能:管理界面+持久化。如下圖:
先來說持久化吧,我們先選擇EFCore作為ORM,所以startup.cs引入identityserver4的方式稍微有點不同,因為要將數(shù)據(jù)保存到數(shù)據(jù)庫中,另外還要引入efcore,來看下startup.cs:
具體代碼如下(偽代碼,主要是思路):
using HyIdentityServer4.Authorization; using HyIdentityServer4.Data; using HyIdentityServer4.Extention; using HyIdentityServer4.Implementation; using IdentityServer4.Services; using IdentityServer4.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.IO; using System.Reflection; using System.Security.Cryptography.X509Certificates; namespace HyIdentityServer4 { public class Startup { public Startup(IConfiguration configuration, IWebHostEnvironment environment) { Configuration = configuration; Environment = environment; } public IConfiguration Configuration { get; } public IWebHostEnvironment Environment { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //session配置 services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); }); //cookie samesite策略 services.AddSameSiteCookiePolicy(); #region 注入EFCore服務(wù)(支持mysql和sqlserver) #if DEBUG string connectionString = Configuration.GetConnectionString("HyIds4ConnectionDebug"); #else string connectionString = Configuration.GetConnectionString("HyIds4ConnectionRelease"); #endif bool isMysql = Configuration.GetConnectionString("IsMysql").ObjToBool(); if (isMysql) { services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion)); } else { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); }; #endregion #region 注入IdentityServer4服務(wù) var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; var builder = services.AddIdentityServer(options => { options.Events.RaiseErrorEvents = true;//是否引發(fā)錯誤事件 options.Events.RaiseInformationEvents = true;//是否引發(fā)信息事件 options.Events.RaiseFailureEvents = true;//是否引發(fā)失敗事件 options.Events.RaiseSuccessEvents = true;//是否引發(fā)成功事件 //用戶交互的選項 options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions { LoginUrl = "/login/index",//登錄地址 }; }) //IdentityServer4使用asp.net identity身份實現(xiàn) .AddAspNetIdentity<IdentityUser>() //IdentityServer4采用EFCore的方式實現(xiàn)數(shù)據(jù)庫模式 .AddConfigurationStore(options => { if (isMysql) { options.ConfigureDbContext = b => b.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion, sql => sql.MigrationsAssembly(migrationsAssembly)); } else { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); } }) // IdentityServer4采用EFCore進(jìn)行一些操作,實現(xiàn)持久化 .AddOperationalStore(options => { if (isMysql) { options.ConfigureDbContext = b => b.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion, sql => sql.MigrationsAssembly(migrationsAssembly)); } else { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); } //是否可以自動清理令牌 options.EnableTokenCleanup = true; //設(shè)置清理的間隔(頻率),以秒為單位 options.TokenCleanupInterval = 15; }); //配置證書 if (Environment.IsDevelopment()) { builder.AddDeveloperSigningCredential(); } else { builder.AddDeveloperSigningCredential(); //builder.AddSigningCredential(new X509Certificate2( // Path.Combine(Environment.ContentRootPath, Configuration["Certificates:CerPath"]), Configuration["Certificates:Password"] // )); } //https://www.javaroad.cn/questions/53540 services.AddTransient<IResourceOwnerPasswordValidator, CustomResourceOwnerPasswordValidator>();//重寫 services.AddTransient<IProfileService, CustomProfileService>();//重寫 services.AddAuthorization(options => { options.AddPolicy("超級管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級管理員"))); }); //實現(xiàn)此接口的類能夠決定是否授權(quán)是被允許的。 services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>();//重寫 #endregion services.AddControllersWithViews(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCookiePolicy(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseSession(); app.UseStaticFiles(); app.UseSession(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=home}/{action=index}/{id?}"); }); } } }
再來說管理界面,上邊也說到identityserver4提供了mvc的界面,如果界面要求不高可以使用這一套UI,如下圖:
當(dāng)然我們也可以自定義管理界面,但是要注意的Account控制器中的action盡量和Quickstart中的保持一致,因為這里面的action涉及到了identityserver4相關(guān)的回調(diào),如果改了名稱,就找不到action了。至于Client、API、IdentityResource、Scope等相關(guān)配置的接口就可以自定義了,只要能正確的寫入數(shù)據(jù)庫就行。
總結(jié)一下:identityserver4本身就是實現(xiàn)了認(rèn)證和授權(quán)相關(guān)的功能,我們這里僅僅是引入identityserver4并對其進(jìn)行相應(yīng)的配置,這里的配置信息可以持久化到數(shù)據(jù)庫,也可以寫死在配置文件Config中。提供的界面(mvc)一方面是支持identityserver4某些授權(quán)方式(比如授權(quán)碼模式、Implict)的回調(diào),回調(diào)的action主要是Account控制器中的action;另一方面是讓管理員配置站外應(yīng)用、作用域、Api資源信息的,如下圖:
三、identityserver4實踐中遇到的問題
1、identityserver4項目中的認(rèn)證
identityserver4項目提供了認(rèn)證授權(quán)相關(guān)的功能,但是如果我們的認(rèn)證授權(quán)項目有了管理界面,如上邊介紹的,就需要管理員,管理員可以配置客戶端、作用域等信息。但是管理員也需要權(quán)限,所以需要引入認(rèn)證相關(guān)模塊,這里使用ASP.NET COREIdentity 。千萬不要混淆以下幾個概念:identityserver4、aspnet core identity、efcore。再啰嗦下,我們利用identityserver4構(gòu)建了認(rèn)證授權(quán)項目,在該項目中我們使用efcore實現(xiàn)持久化,使用aspnet core identity來認(rèn)證管理員的所擁有的權(quán)限。所以在startup.cs的ConfigureServices方法中需要引入aspnetcore identity,代碼如下:
#region 注入Identity服務(wù) //IdentityOptions文檔說明 //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1 //AddIdentity為指定的用戶和角色類型添加并配置身份系統(tǒng)。 services.AddIdentity<IdentityUser, IdentityRole>(options => { options.User = new UserOptions { RequireUniqueEmail = true, //要求Email唯一 AllowedUserNameCharacters = null //允許的用戶名字符 }; options.Password = new PasswordOptions { RequiredLength = 6, //要求密碼最小長度,默認(rèn)是 6 個字符 RequireDigit = false, //要求有數(shù)字 RequiredUniqueChars = 0, //要求至少要出現(xiàn)的字母數(shù) RequireLowercase = false, //要求小寫字母 RequireNonAlphanumeric = false, //要求特殊字符 RequireUppercase = false //要求大寫字母 }; }) //認(rèn)證信息存儲的框架實現(xiàn) .AddEntityFrameworkStores<ApplicationDbContext>() //令牌提供程序,用于生成重置密碼的令牌、更改電子郵件和更改電話號碼操作以及雙因素身份驗證的令牌 .AddDefaultTokenProviders(); //配置應(yīng)用的cookie services.ConfigureApplicationCookie(options => { //重定向 options.LoginPath = new PathString("/login/index"); }); //配置session的有效時間,單位秒 services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(180); }); #endregion
為了簡單,我們不做角色管理了(滿足了我的需求),直接寫死個角色,如下代碼,這樣該項目中的用戶就會涉及到兩個角色:普通用戶、超級管理員角色
services.AddAuthorization(options => { options.AddPolicy("超級管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級管理員"))); });
startup.cs的代碼截圖:
完整的startup.cs代碼如下:
using HyIdentityServer4.Authorization; using HyIdentityServer4.Data; using HyIdentityServer4.Extention; using HyIdentityServer4.Implementation; using IdentityServer4.Services; using IdentityServer4.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.IO; using System.Reflection; using System.Security.Cryptography.X509Certificates; namespace HyIdentityServer4 { public class Startup { public Startup(IConfiguration configuration, IWebHostEnvironment environment) { Configuration = configuration; Environment = environment; } public IConfiguration Configuration { get; } public IWebHostEnvironment Environment { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //session配置 services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); }); //cookie samesite策略 services.AddSameSiteCookiePolicy(); #region 注入EFCore服務(wù) #if DEBUG string connectionString = Configuration.GetConnectionString("HyIds4ConnectionDebug"); #else string connectionString = Configuration.GetConnectionString("HyIds4ConnectionRelease"); #endif bool isMysql = Configuration.GetConnectionString("IsMysql").ObjToBool(); if (isMysql) { services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion)); } else { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); }; #endregion #region 注入Identity服務(wù) //IdentityOptions文檔說明 //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1 //AddIdentity為指定的用戶和角色類型添加并配置身份系統(tǒng)。 services.AddIdentity<IdentityUser, IdentityRole>(options => { options.User = new UserOptions { RequireUniqueEmail = true, //要求Email唯一 AllowedUserNameCharacters = null //允許的用戶名字符 }; options.Password = new PasswordOptions { RequiredLength = 1, //要求密碼最小長度,默認(rèn)是 6 個字符 RequireDigit = false, //要求有數(shù)字 RequiredUniqueChars = 0, //要求至少要出現(xiàn)的字母數(shù) RequireLowercase = false, //要求小寫字母 RequireNonAlphanumeric = false, //要求特殊字符 RequireUppercase = false //要求大寫字母 }; }) //認(rèn)證信息存儲的框架實現(xiàn) .AddEntityFrameworkStores<ApplicationDbContext>() //令牌提供程序,用于生成重置密碼的令牌、更改電子郵件和更改電話號碼操作以及雙因素身份驗證的令牌 .AddDefaultTokenProviders(); //配置應(yīng)用的cookie services.ConfigureApplicationCookie(options => { //重定向 options.LoginPath = new PathString("/login/index"); }); //配置session的有效時間,單位秒 services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(180); }); #endregion #region 注入IdentityServer4服務(wù) var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; var builder = services.AddIdentityServer(options => { options.Events.RaiseErrorEvents = true;//是否引發(fā)錯誤事件 options.Events.RaiseInformationEvents = true;//是否引發(fā)信息事件 options.Events.RaiseFailureEvents = true;//是否引發(fā)失敗事件 options.Events.RaiseSuccessEvents = true;//是否引發(fā)成功事件 //用戶交互的選項 options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions { LoginUrl = "/login/index",//登錄地址 }; }) //IdentityServer4使用asp.net identity身份實現(xiàn) .AddAspNetIdentity<IdentityUser>() //IdentityServer4采用EFCore的方式實現(xiàn)數(shù)據(jù)庫模式 .AddConfigurationStore(options => { if (isMysql) { options.ConfigureDbContext = b => b.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion, sql => sql.MigrationsAssembly(migrationsAssembly)); } else { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); } }) // IdentityServer4采用EFCore進(jìn)行一些操作,實現(xiàn)持久化 .AddOperationalStore(options => { if (isMysql) { options.ConfigureDbContext = b => b.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion, sql => sql.MigrationsAssembly(migrationsAssembly)); } else { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); } //是否可以自動清理令牌 options.EnableTokenCleanup = true; //設(shè)置清理的間隔(頻率),以秒為單位 options.TokenCleanupInterval = 15; }); //配置證書 if (Environment.IsDevelopment()) { builder.AddDeveloperSigningCredential(); } else { builder.AddDeveloperSigningCredential(); //builder.AddSigningCredential(new X509Certificate2( // Path.Combine(Environment.ContentRootPath, Configuration["Certificates:CerPath"]), Configuration["Certificates:Password"] // )); } //https://www.javaroad.cn/questions/53540 services.AddTransient<IResourceOwnerPasswordValidator, CustomResourceOwnerPasswordValidator>(); services.AddTransient<IProfileService, CustomProfileService>(); services.AddAuthorization(options => { options.AddPolicy("超級管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級管理員"))); }); //實現(xiàn)此接口的類能夠決定是否授權(quán)是被允許的。 services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>(); #endregion services.AddControllersWithViews(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCookiePolicy(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseSession(); app.UseStaticFiles(); app.UseSession(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=home}/{action=index}/{id?}"); }); } } }
需要權(quán)限認(rèn)證的action要加上Authorize,如果想了解Authorize做了哪些功能可以看下微軟官網(wǎng)。因為控制器比較多,所以抽象出來一個basecontroller,加上Authorize特性,需要的controller繼承basecontroller,如下:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace HyIdentityServer4.Controllers { //[SecurityHeaders] [Authorize(Policy = "超級管理員")] public class BaseController : Controller { } }
管理員就可以通過用戶管理為用戶配置角色了:
2、Access_Token包含其他聲明
(1)問題
Access_Token是jwt格式的,因為站外應(yīng)用獲取到token后,想要從token中解析出用戶標(biāo)識、用戶郵箱等信息,如何讓identityserver4項目生成的token包含這些信息呢?
(2)解決方法
為了獲得分配給用戶的聲明并將其附加到訪問令牌,需要在授權(quán)服務(wù)上實現(xiàn)兩個接口:IResourceOwnerPasswordValidator
和IProfileService。
以下是對這兩個類的實現(xiàn):(注意請務(wù)必獲取最新版本的IdentityServer4)
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { private readonly UserManager<ApplicationUser> _userManager; public ResourceOwnerPasswordValidator(UserManager<ApplicationUser> userManager) { _userManager = userManager; } public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { var userTask = _userManager.FindByNameAsync(context.UserName); var user = userTask.Result; context.Result = new GrantValidationResult(user.Id, "password", null, "local", null); return Task.FromResult(context.Result); } } 和 public class AspNetIdentityProfileService : IProfileService { private readonly UserManager<ApplicationUser> _userManager; public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager) { _userManager = userManager; } public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var subject = context.Subject; if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); var subjectId = subject.GetSubjectId(); var user = await _userManager.FindByIdAsync(subjectId); if (user == null) throw new ArgumentException("Invalid subject identifier"); var claims = await GetClaimsFromUser(user); var siteIdClaim = claims.SingleOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); context.IssuedClaims.Add(new Claim(JwtClaimTypes.Email, user.Email)); context.IssuedClaims.Add(new Claim("siteid", siteIdClaim.Value)); context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, "User")); var roleClaims = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); foreach (var roleClaim in roleClaims) { context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, roleClaim.Value)); } } public async Task IsActiveAsync(IsActiveContext context) { var subject = context.Subject; if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); var subjectId = subject.GetSubjectId(); var user = await _userManager.FindByIdAsync(subjectId); context.IsActive = false; if (user != null) { if (_userManager.SupportsUserSecurityStamp) { var security_stamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault(); if (security_stamp != null) { var db_security_stamp = await _userManager.GetSecurityStampAsync(user); if (db_security_stamp != security_stamp) return; } } context.IsActive = !user.LockoutEnabled || !user.LockoutEnd.HasValue || user.LockoutEnd <= DateTime.Now; } } private async Task<IEnumerable<Claim>> GetClaimsFromUser(ApplicationUser user) { var claims = new List<Claim> { new Claim(JwtClaimTypes.Subject, user.Id), new Claim(JwtClaimTypes.PreferredUserName, user.UserName) }; if (_userManager.SupportsUserEmail) { claims.AddRange(new[] { new Claim(JwtClaimTypes.Email, user.Email), new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean) }); } if (_userManager.SupportsUserPhoneNumber && !string.IsNullOrWhiteSpace(user.PhoneNumber)) { claims.AddRange(new[] { new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber), new Claim(JwtClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed ? "true" : "false", ClaimValueTypes.Boolean) }); } if (_userManager.SupportsUserClaim) { claims.AddRange(await _userManager.GetClaimsAsync(user)); } if (_userManager.SupportsUserRole) { var roles = await _userManager.GetRolesAsync(user); claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role))); } return claims; } }
然后需要在startup.cs中添加到你的服務(wù):
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>(); services.AddTransient<IProfileService, AspNetIdentityProfileService>();
3、基于identityserver4的授權(quán)項目中自定義生成Token
主要是引入 ITokenService 接口,調(diào)用CreateSecurityTokenAsync方法
代碼如下:
/// <summary> /// 為用戶創(chuàng)建token /// </summary> private async Task<TokenDto> CreateToken(Client client, CreateTokenInput input) { Token accessToken = await CreateAccessToken(client, input); string token = await _tokenService.CreateSecurityTokenAsync(accessToken); return new TokenDto() { AccessToken = token, ExpiresIn = input.Lifetime > 0 ? input.Lifetime : client.AccessTokenLifetime, TokenType = "Bearer" }; } /// <summary> /// 創(chuàng)建生成jwt的Token所包含信息 /// </summary> /// <param name="client"></param> /// <param name="input"></param> /// <returns></returns> private async Task<Token> CreateAccessToken(Client client, CreateTokenInput input) { #region claims //, string subjectId, int lifetime, params string[] scopes var claims = new List<Claim> { new Claim(JwtClaimTypes.ClientId, client.ClientId), new Claim(JwtClaimTypes.Id, input.SubjectId), }; input.Claims?.ForEach(c => claims.Add(c)); input.Scopes?.ForEach(s => claims.Add(new Claim(JwtClaimTypes.Scope, s))); //client scopes claims.AddRange(client.AllowedScopes.Select(s => new Claim(JwtClaimTypes.Scope, s))); #endregion #region aud var website = _configuration.GetValue<string>("AuthWebSite", "").RemoveTrailingSlash(); List<string> aud = new List<string>() { string.Concat(website, "/resources") }; //client aud:apiResourceName var apiResourceNameList = await _identityServer4Service.GetApiResourceNames(client.AllowedScopes.ToList()); aud.AddRange(apiResourceNameList ?? new List<string>()); #endregion var token = new Token(OidcConstants.TokenTypes.AccessToken) { CreationTime = DateTime.UtcNow, Claims = claims, Audiences = aud, Issuer = website, Lifetime = input.Lifetime > 0 ? input.Lifetime : client.AccessTokenLifetime, ClientId = client.ClientId, AccessTokenType = client.AccessTokenType, //Scopes = client.AllowedScopes.ToList(), }; return token; } #endregion
到此這篇關(guān)于IdentityServer4介紹和使用的文章就介紹到這了,更多相關(guān)IdentityServer4介紹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net Repeater 數(shù)據(jù)綁定的具體實現(xiàn)(圖文詳解)
此例子綁定的數(shù)據(jù)源為微軟在mssql2000中提供的Northwind數(shù)據(jù)庫中的表Categories。2013-07-07Entity Framework Core中執(zhí)行SQL語句和存儲過程的方法介紹
這篇文章介紹了Entity Framework Core中執(zhí)行SQL語句和存儲過程的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02relaxlife.net發(fā)布一個自己開發(fā)的中文分詞程序
relaxlife.net發(fā)布一個自己開發(fā)的中文分詞程序...2007-03-03