亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解IdentityServer4介紹和使用

 更新時(shí)間:2022年07月14日 10:45:51   作者:搬磚滴  
在.NETCORE中更傾向于使用Identityserver4組件來(lái)構(gòu)建認(rèn)證授權(quán)服務(wù),原因是IdentityServer4?是為ASP.NET?Core系列量身打造的一款基于?OpenID?Connect?和?OAuth?2.0?認(rèn)證框架,本文重點(diǎn)介紹IDS4實(shí)際使用過(guò)程中涉及到的技術(shù)點(diǎn),感興趣的朋友一起看看吧

一、概述

前幾篇文章介紹到,OWIN提供了一些OAuth2.0認(rèn)證的機(jī)制,使用OWIN可以很方便的實(shí)現(xiàn)OAuth2.0認(rèn)證和授權(quán)。但是在.NETCORE中更傾向于使用Identityserver4組件來(lái)構(gòu)建認(rèn)證授權(quán)服務(wù),原因是IdentityServer4 是為ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 認(rèn)證框架。 具體的可以看下IDS4的官方文檔。本文重點(diǎn)介紹IDS4實(shí)際使用過(guò)程中涉及到的技術(shù)點(diǎn)。下面先簡(jiǎn)單介紹下IDS4中涉及到的概念及簡(jiǎn)單使用流程。

OpenID是Authentication,即認(rèn)證,對(duì)用戶(hù)的身份進(jìn)行認(rèn)證。

OAuth是一個(gè)開(kāi)放標(biāo)準(zhǔn),是Authorization,即授權(quán),允許用戶(hù)授權(quán)第三方移動(dòng)應(yīng)用訪問(wèn)他們存儲(chǔ)在其他服務(wù)商上存儲(chǔ)的私密的資源(如照片,視頻,聯(lián)系人列表),而無(wú)需將用戶(hù)名和密碼提供給第三方應(yīng)用。OAuth允許用戶(hù)提供一個(gè)令牌而不是用戶(hù)名和密碼來(lái)訪問(wèn)他們存放在特定服務(wù)商上的數(shù)據(jù)。每一個(gè)令牌授權(quán)一個(gè)特定的網(wǎng)站內(nèi)訪問(wèn)特定的資源(例如僅僅是某一相冊(cè)中的視頻)。這樣,OAuth可以允許用戶(hù)授權(quán)第三方網(wǎng)站訪問(wèn)他們存儲(chǔ)在另外服務(wù)提供者的某些特定信息,而非所有內(nèi)容。

OIDC是OpenID Connect的簡(jiǎn)稱(chēng),是一個(gè)基于OAuth2協(xié)議的身份認(rèn)證標(biāo)準(zhǔn)協(xié)議。是認(rèn)證和授權(quán)的結(jié)合。OAuth2是一個(gè)授權(quán)協(xié)議,它無(wú)法提供完善的身份認(rèn)證功能,OIDC使用OAuth2的授權(quán)服務(wù)器來(lái)為第三方客戶(hù)端提供用戶(hù)的身份認(rèn)證,并把對(duì)應(yīng)的身份認(rèn)證信息傳遞給客戶(hù)端,且可以適用于各種類(lèi)型的客戶(hù)端(比如服務(wù)端應(yīng)用,移動(dòng)APP,JS應(yīng)用),且完全兼容OAuth2,也就是說(shuō)你搭建了一個(gè)OIDC的服務(wù)后,也可以當(dāng)作一個(gè)OAuth2的服務(wù)來(lái)用。

1、OpenID認(rèn)證用戶(hù)的流程

  • 用戶(hù)訪問(wèn)xxx.com(該網(wǎng)站支持OpenID)
  • xxx.com將用戶(hù)導(dǎo)向OpenID服務(wù)的登錄頁(yè)面
  • 用戶(hù)輸入用戶(hù)名密碼,成功后回調(diào)到xxx.com網(wǎng)站,并攜帶用戶(hù)在OpenID服務(wù)中的唯一標(biāo)識(shí)(這個(gè)表示可能僅僅是一個(gè)GUID,不含用戶(hù)個(gè)人信息)
  • xxx.com校檢成功后,就認(rèn)為用戶(hù)完成了登錄認(rèn)證

OpenID 目的就是做認(rèn)證,使用簡(jiǎn)單,不透露用戶(hù)的個(gè)人信息。

2、OAuth認(rèn)證用戶(hù)的流程

OAuth是用來(lái)做授權(quán)的,如果用來(lái)做認(rèn)證,具體的流程如下圖所示:

  • 用戶(hù)使用QQ登錄HelloFont
  • HelloFont將用戶(hù)導(dǎo)向QQ授權(quán)服務(wù)的登錄頁(yè)面
  • 用戶(hù)登錄成功并授權(quán)后,頁(yè)面返回HelloFont并攜帶訪問(wèn)令牌
  • 如果想獲取用戶(hù)的詳細(xì)信息,還需要通過(guò)訪問(wèn)令牌再次調(diào)用QQ服務(wù)提供的相關(guān)接口進(jìn)行請(qǐng)求

可以看出,OAuth 相對(duì)于 OpenID 最大的區(qū)別就是,網(wǎng)站實(shí)際上是拿到了用戶(hù)帳戶(hù)訪問(wèn)權(quán)限繼而確認(rèn)你的身份。同時(shí)OAuth還比OpenID多了幾個(gè)操作步驟。

3、IdentityServer4對(duì)象

下面簡(jiǎn)單的介紹下Identityserver4中涉及的對(duì)象,具體的可以參考下官方文檔

  • 用戶(hù)(User):用戶(hù)是使用已注冊(cè)的客戶(hù)端(指在id4中已經(jīng)注冊(cè))訪問(wèn)資源的人。
  • 客戶(hù)端(Client):客戶(hù)端就是從identityserver請(qǐng)求令牌的軟件(你可以理解為一個(gè)app即可),既可以通過(guò)身份認(rèn)證令牌來(lái)驗(yàn)證識(shí)別用戶(hù)身份,又可以通過(guò)授權(quán)令牌來(lái)訪問(wèn)服務(wù)端的資源。但是客戶(hù)端首先必須在申請(qǐng)令牌前已經(jīng)在identityserver服務(wù)中注冊(cè)過(guò)。
  • 資源(Resources):資源就是你想用identityserver保護(hù)的東東,可以是用戶(hù)的身份數(shù)據(jù)或者api資源。
  • 身份令牌(顧名思義用于做身份認(rèn)證,例如sso其實(shí)主要就是用于身份認(rèn)證):一個(gè)身份令牌指的就是對(duì)認(rèn)證過(guò)程的描述。它至少要標(biāo)識(shí)某個(gè)用戶(hù)(Called the sub aka subject claim)的主身份信息,和該用戶(hù)的認(rèn)證時(shí)間和認(rèn)證方式。但是身份令牌可以包含額外的身份數(shù)據(jù),具體開(kāi)發(fā)者可以自行設(shè)定,但是一般情況為了確保數(shù)據(jù)傳輸?shù)男?,開(kāi)發(fā)者一般不做過(guò)多額外的設(shè)置,大家也可以根據(jù)使用場(chǎng)景自行決定。
  • 訪問(wèn)令牌(用于做客戶(hù)端訪問(wèn)授權(quán)):訪問(wèn)令牌允許客戶(hù)端訪問(wèn)某個(gè) API 資源??蛻?hù)端請(qǐng)求到訪問(wèn)令牌,然后使用這個(gè)令牌來(lái)訪問(wèn) API資源。訪問(wèn)令牌包含了客戶(hù)端和用戶(hù)(如果有的話,這取決于業(yè)務(wù)是否需要,但通常不必要)的相關(guān)信息,API通過(guò)這些令牌信息來(lái)授予客戶(hù)端的數(shù)據(jù)訪問(wèn)權(quán)限。

二、IdentityServer4實(shí)踐

1、構(gòu)建非持久化認(rèn)證服務(wù)項(xiàng)目

下面簡(jiǎn)單的介紹下大體流程,看懂思路即可:

IDS4本身已經(jīng)將OAuth2.0+OIDC+SSO思想給實(shí)現(xiàn)了,并且提供了成熟的組件IdentityServer4,如下圖,只需要將該組件引入,進(jìn)行相關(guān)的配置即可。

正常來(lái)說(shuō)我們通過(guò)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、客戶(hù)端Clients等等吧,具體怎么配置建議還是看一下官方文檔,很詳細(xì),里面包含了相應(yīng)的屬性及示例等,可根據(jù)實(shí)際需求進(jìn)行選擇配置。

簡(jiǎn)單的認(rèn)證服務(wù)就搭建好了,當(dāng)然這個(gè)是比較簡(jiǎn)單的,支持客戶(hù)端模式,不需要用戶(hù)參與的授權(quán)。如果說(shuō)站外應(yīng)用需要使用授權(quán)碼模式、或者implact模式,我們搭建的identityserver4項(xiàng)目還要提供登錄授權(quán)等相關(guān)的頁(yè)面的,不然用戶(hù)在哪里登錄和授權(quán)呢。這個(gè)官網(wǎng)也有示例,可以在github中搜索下載源碼查看,直接使用他們提供的界面(mvc)即可,當(dāng)然也可以自己進(jìn)行UI優(yōu)化,但是他們提供的action的名稱(chēng)最好不要更改,因?yàn)閕dentityserver4包中退出、登錄相關(guān)的跳轉(zhuǎn)都是指定好的,通過(guò)示例來(lái)說(shuō)明下為啥不建議改動(dòng):

截圖中是identityserver4提供的界面代碼(mvc),有個(gè)Account控制器,里面有退出登錄、授權(quán)受限等action,如果站外應(yīng)用使用授權(quán)碼模式登錄,發(fā)現(xiàn)授權(quán)受限或者用戶(hù)退出登錄,那么identityserver4服務(wù)會(huì)將用戶(hù)指向Account/Logout或者Account/AccessDenied,如果把名稱(chēng)改了就找不到相應(yīng)的action了。當(dāng)然并不是所有的都不能改,比方說(shuō)登錄,我就自己?jiǎn)为?dú)在另一個(gè)action寫(xiě)的(有需求的原因),所以需要在startup.cs引入identityserver4的時(shí)候指定,如下:

//用戶(hù)交互的選項(xiàng)
options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
{
    LoginUrl = "/login/index",//登錄地址
};

還有一種辦法就是將identityserver4源碼進(jìn)行二次開(kāi)發(fā),改成你想要的樣子。。。

2、構(gòu)建持久化認(rèn)證服務(wù)項(xiàng)目

上邊的identityserver4的配置信息是寫(xiě)死在文件中的,在實(shí)際開(kāi)發(fā)中,還是要將配置信息寫(xiě)入到數(shù)據(jù)庫(kù)中,所以就需要持久化了。另外還需要提供人為配置信息的管理界面??偟膩?lái)說(shuō)就是基于IdentityServer4包加兩塊功能:管理界面+持久化。如下圖:

先來(lái)說(shuō)持久化吧,我們先選擇EFCore作為ORM,所以startup.cs引入identityserver4的方式稍微有點(diǎn)不同,因?yàn)橐獙?shù)據(jù)保存到數(shù)據(jù)庫(kù)中,另外還要引入efcore,來(lái)看下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ā)錯(cuò)誤事件
                options.Events.RaiseInformationEvents = true;//是否引發(fā)信息事件
                options.Events.RaiseFailureEvents = true;//是否引發(fā)失敗事件
                options.Events.RaiseSuccessEvents = true;//是否引發(fā)成功事件             
                //用戶(hù)交互的選項(xiàng)
                options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
                {
                    LoginUrl = "/login/index",//登錄地址
                };
            })
            //IdentityServer4使用asp.net identity身份實(shí)現(xiàn)
            .AddAspNetIdentity<IdentityUser>()
            //IdentityServer4采用EFCore的方式實(shí)現(xiàn)數(shù)據(jù)庫(kù)模式
            .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)行一些操作,實(shí)現(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));
                }

                //是否可以自動(dòng)清理令牌
                options.EnableTokenCleanup = true;
                //設(shè)置清理的間隔(頻率),以秒為單位
                options.TokenCleanupInterval = 15;
            });
            //配置證書(shū)
            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>();//重寫(xiě)
            services.AddTransient<IProfileService, CustomProfileService>();//重寫(xiě)
            services.AddAuthorization(options =>
            {
                options.AddPolicy("超級(jí)管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級(jí)管理員")));
            });
            //實(shí)現(xiàn)此接口的類(lèi)能夠決定是否授權(quán)是被允許的。
            services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>();//重寫(xiě)
            #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?}");
            });
        }
    }
}

再來(lái)說(shuō)管理界面,上邊也說(shuō)到identityserver4提供了mvc的界面,如果界面要求不高可以使用這一套UI,如下圖:

當(dāng)然我們也可以自定義管理界面,但是要注意的Account控制器中的action盡量和Quickstart中的保持一致,因?yàn)檫@里面的action涉及到了identityserver4相關(guān)的回調(diào),如果改了名稱(chēng),就找不到action了。至于Client、API、IdentityResource、Scope等相關(guān)配置的接口就可以自定義了,只要能正確的寫(xiě)入數(shù)據(jù)庫(kù)就行。

總結(jié)一下:identityserver4本身就是實(shí)現(xiàn)了認(rèn)證和授權(quán)相關(guān)的功能,我們這里僅僅是引入identityserver4并對(duì)其進(jìn)行相應(yīng)的配置,這里的配置信息可以持久化到數(shù)據(jù)庫(kù),也可以寫(xiě)死在配置文件Config中。提供的界面(mvc)一方面是支持identityserver4某些授權(quán)方式(比如授權(quán)碼模式、Implict)的回調(diào),回調(diào)的action主要是Account控制器中的action;另一方面是讓管理員配置站外應(yīng)用、作用域、Api資源信息的,如下圖:

三、identityserver4實(shí)踐中遇到的問(wèn)題

1、identityserver4項(xiàng)目中的認(rèn)證

identityserver4項(xiàng)目提供了認(rèn)證授權(quán)相關(guān)的功能,但是如果我們的認(rèn)證授權(quán)項(xiàng)目有了管理界面,如上邊介紹的,就需要管理員,管理員可以配置客戶(hù)端、作用域等信息。但是管理員也需要權(quán)限,所以需要引入認(rèn)證相關(guān)模塊,這里使用ASP.NET COREIdentity 。千萬(wàn)不要混淆以下幾個(gè)概念:identityserver4、aspnet core identity、efcore。再啰嗦下,我們利用identityserver4構(gòu)建了認(rèn)證授權(quán)項(xiàng)目,在該項(xiàng)目中我們使用efcore實(shí)現(xiàn)持久化,使用aspnet core identity來(lái)認(rèn)證管理員的所擁有的權(quán)限。所以在startup.cs的ConfigureServices方法中需要引入aspnetcore identity,代碼如下:

#region 注入Identity服務(wù)
   //IdentityOptions文檔說(shuō)明
   //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1
   //AddIdentity為指定的用戶(hù)和角色類(lèi)型添加并配置身份系統(tǒng)。
   services.AddIdentity<IdentityUser, IdentityRole>(options =>
   {
       options.User = new UserOptions
       {
             RequireUniqueEmail = true, //要求Email唯一
             AllowedUserNameCharacters = null //允許的用戶(hù)名字符
       };
       options.Password = new PasswordOptions
       {
            RequiredLength = 6, //要求密碼最小長(zhǎng)度,默認(rèn)是 6 個(gè)字符
            RequireDigit = false, //要求有數(shù)字
            RequiredUniqueChars = 0, //要求至少要出現(xiàn)的字母數(shù)
            RequireLowercase = false, //要求小寫(xiě)字母
            RequireNonAlphanumeric = false, //要求特殊字符
            RequireUppercase = false //要求大寫(xiě)字母
        };
    })
    //認(rèn)證信息存儲(chǔ)的框架實(shí)現(xiàn)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    //令牌提供程序,用于生成重置密碼的令牌、更改電子郵件和更改電話號(hào)碼操作以及雙因素身份驗(yàn)證的令牌
    .AddDefaultTokenProviders();
    //配置應(yīng)用的cookie
    services.ConfigureApplicationCookie(options =>
    {
        //重定向
        options.LoginPath = new PathString("/login/index");
     });
     //配置session的有效時(shí)間,單位秒
     services.AddSession(options =>
     {
         options.IdleTimeout = TimeSpan.FromSeconds(180);
     });
     #endregion

為了簡(jiǎn)單,我們不做角色管理了(滿(mǎn)足了我的需求),直接寫(xiě)死個(gè)角色,如下代碼,這樣該項(xiàng)目中的用戶(hù)就會(huì)涉及到兩個(gè)角色:普通用戶(hù)、超級(jí)管理員角色

services.AddAuthorization(options =>
{
     options.AddPolicy("超級(jí)管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級(jí)管理員")));
});

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文檔說(shuō)明
            //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1
            //AddIdentity為指定的用戶(hù)和角色類(lèi)型添加并配置身份系統(tǒng)。
            services.AddIdentity<IdentityUser, IdentityRole>(options =>
            {
                options.User = new UserOptions
                {
                    RequireUniqueEmail = true, //要求Email唯一
                    AllowedUserNameCharacters = null //允許的用戶(hù)名字符
                };
                options.Password = new PasswordOptions
                {
                    RequiredLength = 1, //要求密碼最小長(zhǎng)度,默認(rèn)是 6 個(gè)字符
                    RequireDigit = false, //要求有數(shù)字
                    RequiredUniqueChars = 0, //要求至少要出現(xiàn)的字母數(shù)
                    RequireLowercase = false, //要求小寫(xiě)字母
                    RequireNonAlphanumeric = false, //要求特殊字符
                    RequireUppercase = false //要求大寫(xiě)字母
                };
            })
            //認(rèn)證信息存儲(chǔ)的框架實(shí)現(xiàn)
            .AddEntityFrameworkStores<ApplicationDbContext>()
            //令牌提供程序,用于生成重置密碼的令牌、更改電子郵件和更改電話號(hào)碼操作以及雙因素身份驗(yàn)證的令牌
            .AddDefaultTokenProviders();
            //配置應(yīng)用的cookie
            services.ConfigureApplicationCookie(options =>
            {
                //重定向
                options.LoginPath = new PathString("/login/index");
            });
            //配置session的有效時(shí)間,單位秒
            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ā)錯(cuò)誤事件
                options.Events.RaiseInformationEvents = true;//是否引發(fā)信息事件
                options.Events.RaiseFailureEvents = true;//是否引發(fā)失敗事件
                options.Events.RaiseSuccessEvents = true;//是否引發(fā)成功事件             
                //用戶(hù)交互的選項(xiàng)
                options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
                {
                    LoginUrl = "/login/index",//登錄地址
                };
            })
            //IdentityServer4使用asp.net identity身份實(shí)現(xiàn)
            .AddAspNetIdentity<IdentityUser>()
            //IdentityServer4采用EFCore的方式實(shí)現(xiàn)數(shù)據(jù)庫(kù)模式
            .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)行一些操作,實(shí)現(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));
                }

                //是否可以自動(dòng)清理令牌
                options.EnableTokenCleanup = true;
                //設(shè)置清理的間隔(頻率),以秒為單位
                options.TokenCleanupInterval = 15;
            });
            //配置證書(shū)
            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("超級(jí)管理員", policy => policy.Requirements.Add(new ClaimRequirement("rolename", "超級(jí)管理員")));
            });
            //實(shí)現(xiàn)此接口的類(lèi)能夠決定是否授權(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)。因?yàn)榭刂破鞅容^多,所以抽象出來(lái)一個(gè)basecontroller,加上Authorize特性,需要的controller繼承basecontroller,如下:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace HyIdentityServer4.Controllers
{
    //[SecurityHeaders]
    [Authorize(Policy = "超級(jí)管理員")]
    public class BaseController : Controller
    {

    }
}

管理員就可以通過(guò)用戶(hù)管理為用戶(hù)配置角色了:

2、Access_Token包含其他聲明

(1)問(wèn)題

Access_Token是jwt格式的,因?yàn)檎就鈶?yīng)用獲取到token后,想要從token中解析出用戶(hù)標(biāo)識(shí)、用戶(hù)郵箱等信息,如何讓identityserver4項(xiàng)目生成的token包含這些信息呢?

(2)解決方法

為了獲得分配給用戶(hù)的聲明并將其附加到訪問(wèn)令牌,需要在授權(quán)服務(wù)上實(shí)現(xiàn)兩個(gè)接口:IResourceOwnerPasswordValidatorIProfileService。以下是對(duì)這兩個(gè)類(lèi)的實(shí)現(xiàn):(注意請(qǐng)務(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)項(xiàng)目中自定義生成Token

主要是引入 ITokenService 接口,調(diào)用CreateSecurityTokenAsync方法

代碼如下:

/// <summary>
    /// 為用戶(hù)創(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論