详解IdentityServer4介绍和使用

目录
  • 一、概述
    • 1、OpenID认证用户的流程
    • 2、OAuth认证用户的流程
    • 3、IdentityServer4对象
  • 二、IdentityServer4实践
    • 1、构建非持久化认证服务项目
    • 2、构建持久化认证服务项目
  • 三、identityserver4实践中遇到的问题
    • 1、identityserver4项目中的认证
    • 2、Access_Token包含其他声明
    • 3、基于identityserver4的授权项目中自定义生成Token

一、概述

前几篇文章介绍到,OWIN提供了一些OAuth2.0认证的机制,使用OWIN可以很方便的实现OAuth2.0认证和授权。但是在.NETCORE中更倾向于使用Identityserver4组件来构建认证授权服务,原因是IdentityServer4 是为ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 认证框架。 具体的可以看下IDS4的官方文档。本文重点介绍IDS4实际使用过程中涉及到的技术点。下面先简单介绍下IDS4中涉及到的概念及简单使用流程。

OpenID是Authentication,即认证,对用户的身份进行认证。

OAuth是一个开放标准,是Authorization,即授权,允许用户授权第三方移动应用访问他们存储在其他服务商上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。OAuth允许用户提供一个令牌而不是用户名和密码来访问他们存放在特定服务商上的数据。每一个令牌授权一个特定的网站内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth可以允许用户授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

OIDC是OpenID Connect的简称,是一个基于OAuth2协议的身份认证标准协议。是认证和授权的结合。OAuth2是一个授权协议,它无法提供完善的身份认证功能,OIDC使用OAuth2的授权服务器来为第三方客户端提供用户的身份认证,并把对应的身份认证信息传递给客户端,且可以适用于各种类型的客户端(比如服务端应用,移动APP,JS应用),且完全兼容OAuth2,也就是说你搭建了一个OIDC的服务后,也可以当作一个OAuth2的服务来用。

1、OpenID认证用户的流程

  • 用户访问xxx.com(该网站支持OpenID)
  • xxx.com将用户导向OpenID服务的登录页面
  • 用户输入用户名密码,成功后回调到xxx.com网站,并携带用户在OpenID服务中的唯一标识(这个表示可能仅仅是一个GUID,不含用户个人信息)
  • xxx.com校检成功后,就认为用户完成了登录认证

OpenID 目的就是做认证,使用简单,不透露用户的个人信息。

2、OAuth认证用户的流程

OAuth是用来做授权的,如果用来做认证,具体的流程如下图所示:

  • 用户使用QQ登录HelloFont
  • HelloFont将用户导向QQ授权服务的登录页面
  • 用户登录成功并授权后,页面返回HelloFont并携带访问令牌
  • 如果想获取用户的详细信息,还需要通过访问令牌再次调用QQ服务提供的相关接口进行请求

可以看出,OAuth 相对于 OpenID 最大的区别就是,网站实际上是拿到了用户帐户访问权限继而确认你的身份。同时OAuth还比OpenID多了几个操作步骤。

3、IdentityServer4对象

下面简单的介绍下Identityserver4中涉及的对象,具体的可以参考下官方文档

  • 用户(User):用户是使用已注册的客户端(指在id4中已经注册)访问资源的人。
  • 客户端(Client):客户端就是从identityserver请求令牌的软件(你可以理解为一个app即可),既可以通过身份认证令牌来验证识别用户身份,又可以通过授权令牌来访问服务端的资源。但是客户端首先必须在申请令牌前已经在identityserver服务中注册过。
  • 资源(Resources):资源就是你想用identityserver保护的东东,可以是用户的身份数据或者api资源。
  • 身份令牌(顾名思义用于做身份认证,例如sso其实主要就是用于身份认证):一个身份令牌指的就是对认证过程的描述。它至少要标识某个用户(Called the sub aka subject claim)的主身份信息,和该用户的认证时间和认证方式。但是身份令牌可以包含额外的身份数据,具体开发者可以自行设定,但是一般情况为了确保数据传输的效率,开发者一般不做过多额外的设置,大家也可以根据使用场景自行决定。
  • 访问令牌(用于做客户端访问授权):访问令牌允许客户端访问某个 API 资源。客户端请求到访问令牌,然后使用这个令牌来访问 API资源。访问令牌包含了客户端和用户(如果有的话,这取决于业务是否需要,但通常不必要)的相关信息,API通过这些令牌信息来授予客户端的数据访问权限。

二、IdentityServer4实践

1、构建非持久化认证服务项目

下面简单的介绍下大体流程,看懂思路即可:

IDS4本身已经将OAuth2.0+OIDC+SSO思想给实现了,并且提供了成熟的组件IdentityServer4,如下图,只需要将该组件引入,进行相关的配置即可。

正常来说我们通过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等等吧,具体怎么配置建议还是看一下官方文档,很详细,里面包含了相应的属性及示例等,可根据实际需求进行选择配置。

简单的认证服务就搭建好了,当然这个是比较简单的,支持客户端模式,不需要用户参与的授权。如果说站外应用需要使用授权码模式、或者implact模式,我们搭建的identityserver4项目还要提供登录授权等相关的页面的,不然用户在哪里登录和授权呢。这个官网也有示例,可以在github中搜索下载源码查看,直接使用他们提供的界面(mvc)即可,当然也可以自己进行UI优化,但是他们提供的action的名称最好不要更改,因为identityserver4包中退出、登录相关的跳转都是指定好的,通过示例来说明下为啥不建议改动:

截图中是identityserver4提供的界面代码(mvc),有个Account控制器,里面有退出登录、授权受限等action,如果站外应用使用授权码模式登录,发现授权受限或者用户退出登录,那么identityserver4服务会将用户指向Account/Logout或者Account/AccessDenied,如果把名称改了就找不到相应的action了。当然并不是所有的都不能改,比方说登录,我就自己单独在另一个action写的(有需求的原因),所以需要在startup.cs引入identityserver4的时候指定,如下:

//用户交互的选项
options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
{
    LoginUrl = "/login/index",//登录地址
};

还有一种办法就是将identityserver4源码进行二次开发,改成你想要的样子。。。

2、构建持久化认证服务项目

上边的identityserver4的配置信息是写死在文件中的,在实际开发中,还是要将配置信息写入到数据库中,所以就需要持久化了。另外还需要提供人为配置信息的管理界面。总的来说就是基于IdentityServer4包加两块功能:管理界面+持久化。如下图:

先来说持久化吧,我们先选择EFCore作为ORM,所以startup.cs引入identityserver4的方式稍微有点不同,因为要将数据保存到数据库中,另外还要引入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服务(支持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服务
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;//是否引发错误事件
                options.Events.RaiseInformationEvents = true;//是否引发信息事件
                options.Events.RaiseFailureEvents = true;//是否引发失败事件
                options.Events.RaiseSuccessEvents = true;//是否引发成功事件
                //用户交互的选项
                options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
                {
                    LoginUrl = "/login/index",//登录地址
                };
            })
            //IdentityServer4使用asp.net identity身份实现
            .AddAspNetIdentity<IdentityUser>()
            //IdentityServer4采用EFCore的方式实现数据库模式
            .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进行一些操作,实现持久化
            .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;
                //设置清理的间隔(频率),以秒为单位
                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", "超级管理员")));
            });
            //实现此接口的类能够决定是否授权是被允许的。
            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,如下图:

当然我们也可以自定义管理界面,但是要注意的Account控制器中的action尽量和Quickstart中的保持一致,因为这里面的action涉及到了identityserver4相关的回调,如果改了名称,就找不到action了。至于Client、API、IdentityResource、Scope等相关配置的接口就可以自定义了,只要能正确的写入数据库就行。

总结一下:identityserver4本身就是实现了认证和授权相关的功能,我们这里仅仅是引入identityserver4并对其进行相应的配置,这里的配置信息可以持久化到数据库,也可以写死在配置文件Config中。提供的界面(mvc)一方面是支持identityserver4某些授权方式(比如授权码模式、Implict)的回调,回调的action主要是Account控制器中的action;另一方面是让管理员配置站外应用、作用域、Api资源信息的,如下图:

三、identityserver4实践中遇到的问题

1、identityserver4项目中的认证

identityserver4项目提供了认证授权相关的功能,但是如果我们的认证授权项目有了管理界面,如上边介绍的,就需要管理员,管理员可以配置客户端、作用域等信息。但是管理员也需要权限,所以需要引入认证相关模块,这里使用ASP.NET COREIdentity 。千万不要混淆以下几个概念:identityserver4、aspnet core identity、efcore。再啰嗦下,我们利用identityserver4构建了认证授权项目,在该项目中我们使用efcore实现持久化,使用aspnet core identity来认证管理员的所拥有的权限。所以在startup.cs的ConfigureServices方法中需要引入aspnetcore identity,代码如下:

#region 注入Identity服务
   //IdentityOptions文档说明
   //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1
   //AddIdentity为指定的用户和角色类型添加并配置身份系统。
   services.AddIdentity<IdentityUser, IdentityRole>(options =>
   {
       options.User = new UserOptions
       {
             RequireUniqueEmail = true, //要求Email唯一
             AllowedUserNameCharacters = null //允许的用户名字符
       };
       options.Password = new PasswordOptions
       {
            RequiredLength = 6, //要求密码最小长度,默认是 6 个字符
            RequireDigit = false, //要求有数字
            RequiredUniqueChars = 0, //要求至少要出现的字母数
            RequireLowercase = false, //要求小写字母
            RequireNonAlphanumeric = false, //要求特殊字符
            RequireUppercase = false //要求大写字母
        };
    })
    //认证信息存储的框架实现
    .AddEntityFrameworkStores<ApplicationDbContext>()
    //令牌提供程序,用于生成重置密码的令牌、更改电子邮件和更改电话号码操作以及双因素身份验证的令牌
    .AddDefaultTokenProviders();
    //配置应用的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服务
#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服务
            //IdentityOptions文档说明
            //https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.builder.identityoptions?view=aspnetcore-1.1
            //AddIdentity为指定的用户和角色类型添加并配置身份系统。
            services.AddIdentity<IdentityUser, IdentityRole>(options =>
            {
                options.User = new UserOptions
                {
                    RequireUniqueEmail = true, //要求Email唯一
                    AllowedUserNameCharacters = null //允许的用户名字符
                };
                options.Password = new PasswordOptions
                {
                    RequiredLength = 1, //要求密码最小长度,默认是 6 个字符
                    RequireDigit = false, //要求有数字
                    RequiredUniqueChars = 0, //要求至少要出现的字母数
                    RequireLowercase = false, //要求小写字母
                    RequireNonAlphanumeric = false, //要求特殊字符
                    RequireUppercase = false //要求大写字母
                };
            })
            //认证信息存储的框架实现
            .AddEntityFrameworkStores<ApplicationDbContext>()
            //令牌提供程序,用于生成重置密码的令牌、更改电子邮件和更改电话号码操作以及双因素身份验证的令牌
            .AddDefaultTokenProviders();
            //配置应用的cookie
            services.ConfigureApplicationCookie(options =>
            {
                //重定向
                options.LoginPath = new PathString("/login/index");
            });
            //配置session的有效时间,单位秒
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds(180);
            });
            #endregion

            #region 注入IdentityServer4服务
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;//是否引发错误事件
                options.Events.RaiseInformationEvents = true;//是否引发信息事件
                options.Events.RaiseFailureEvents = true;//是否引发失败事件
                options.Events.RaiseSuccessEvents = true;//是否引发成功事件
                //用户交互的选项
                options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
                {
                    LoginUrl = "/login/index",//登录地址
                };
            })
            //IdentityServer4使用asp.net identity身份实现
            .AddAspNetIdentity<IdentityUser>()
            //IdentityServer4采用EFCore的方式实现数据库模式
            .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进行一些操作,实现持久化
            .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;
                //设置清理的间隔(频率),以秒为单位
                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", "超级管理员")));
            });
            //实现此接口的类能够决定是否授权是被允许的。
            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?}");
            });
        }
    }
}

需要权限认证的action要加上Authorize,如果想了解Authorize做了哪些功能可以看下微软官网。因为控制器比较多,所以抽象出来一个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格式的,因为站外应用获取到token后,想要从token中解析出用户标识、用户邮箱等信息,如何让identityserver4项目生成的token包含这些信息呢?

(2)解决方法

为了获得分配给用户的声明并将其附加到访问令牌,需要在授权服务上实现两个接口:IResourceOwnerPasswordValidatorIProfileService。以下是对这两个类的实现:(注意请务必获取最新版本的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中添加到你的服务:

services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, AspNetIdentityProfileService>();

3、基于identityserver4的授权项目中自定义生成Token

主要是引入 ITokenService 接口,调用CreateSecurityTokenAsync方法

代码如下:

/// <summary>
    /// 为用户创建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>
    /// 创建生成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

到此这篇关于IdentityServer4介绍和使用的文章就介绍到这了,更多相关IdentityServer4介绍内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于.net4.0实现IdentityServer4客户端JWT解密

    情景:公司项目基于.net4.0,web客户端实现单点登录需要自己解密id_token,对于jwt解密,.net提供了IdentityModel类库,但是4.0中该类库不可用,所以自己实现了解密方法.. 使用了类库:链接地址 下面直接贴代码,直接调用DecodeJWT方法就行,参数为id_token,key默认为空字符串"", 代码 public static IDictionary<string, object> DecodeJWT(string jwttoken,str

  • IdentityServer4实现.Net Core API接口权限认证(快速入门)

    什么是IdentityServer4 官方解释:IdentityServer4是基于ASP.NET Core实现的认证和授权框架,是对OpenID Connect和OAuth 2.0协议的实现. 通俗来讲,就是服务端对需要认证授权的资源(客户端请求资源)在外层使用IdentityServer4框架进行封装加壳,用户只能通过获取IdentityServer4颁发的Token令牌才能进行资源访问. 下面开始进入正题,如何快速搭建实现API接口鉴权. 准备:1.下载准备NetCore sdk环境 2.

  • IdentityServer4 QuckStart 授权与自定义Claims的问题

    最近在折腾IdentityServer4,为了简单,直接使用了官方给的QuickStart示例项目作为基础进行搭建.有一说一,为了保护一个API,感觉花费的时间比写一个API还要多. 本文基于ASP.NET CORE 3.1, IdentityServer4 3.1.3.代码皆为关键代码,贴全了太多了. 好不容易跑起来了,最终的任务要落实到授权的工作上来.在API中使用Authorize用来限制用户的访问. [Route("api/[controller]")] [Authorize(

  • 详解IdentityServer4介绍和使用

    目录 一.概述 1.OpenID认证用户的流程 2.OAuth认证用户的流程 3.IdentityServer4对象 二.IdentityServer4实践 1.构建非持久化认证服务项目 2.构建持久化认证服务项目 三.identityserver4实践中遇到的问题 1.identityserver4项目中的认证 2.Access_Token包含其他声明 3.基于identityserver4的授权项目中自定义生成Token 一.概述 前几篇文章介绍到,OWIN提供了一些OAuth2.0认证的机

  • 两分钟让你彻底明白Android Activity生命周期的详解(图文介绍)

    大家好,今天给大家详解一下Android中Activity的生命周期,我在前面也曾经讲过这方面的内容,但是像网上大多数文章一样,基本都是翻译Android API,过于笼统,相信大家看了,会有一点点的帮助 ,但是还不能完全吃透,所以我今天特意在重新总结一下.首先看一下Android api中所提供的Activity生命周期图(不明白的,可以看完整篇文章,在回头看一下这个图,你会明白的): Activity其实是继承了ApplicationContext这个类,我们可以重写以下方法,如下代码: 复

  • 详解webpack介绍&安装&常用命令

    webpack系列目录 webpack 系列 二:webpack 介绍&安装 webpack 系列 三:webpack 如何集成第三方js库 webpack 系列 四:webpack 多页面支持 & 公共组件单独打包 webpack 系列 五:webpack Loaders 模块加载器 webpack 系列 六:前端项目模板-webpack+gulp实现自动构建部署 基于webpack搭建纯静态页面型前端工程解决方案模板, 最终形态源码见github: https://github.com

  • java中四种生成和解析XML文档的方法详解(介绍+优缺点比较+示例)

    众所周知,现在解析XML的方法越来越多,但主流的方法也就四种,即:DOM.SAX.JDOM和DOM4J 下面首先给出这四种方法的jar包下载地址 DOM:在现在的Java JDK里都自带了,在xml-apis.jar包里 SAX:http://sourceforge.net/projects/sax/ JDOM:http://jdom.org/downloads/index.html DOM4J:http://sourceforge.net/projects/dom4j/  一.介绍及优缺点分析

  • 5个HTML5的常用本地存储方式详解与介绍

    在 HTML5 规范之前,存储主要是用 cookies .但cookies也有缺点: 在请求头上带着数据: 大小是 4k 之内: 主 Domain 污染: cookies 的主要应用:购物车.客户登录. 由于存在这么多缺点,因此我们需要解决以下问题: 解决 4k 的大小问题: 解决请求头常带存储信息的问题: 解决关系型存储的问题: 跨浏览器: HTML5的5种存储方式 1. 本地存储 localstorage 存储方式: 以键值对( Key-Value)的方式存储,永久存储,永不失效,除非手动删

  • PHP 中魔术常量的实例详解

    PHP 中魔术常量的实例详解 本文介绍下,php编程中的魔术常量,掌握并灵活应用这些方法与常量,对于提高php的编程水平,有很大的帮助.有需要的朋友参考学习下. 魔术常量: namespace ns1; class Test { function __construct() { var_dump(__LINE__); var_dump(__FILE__); var_dump(__DIR__); var_dump(__FUNCTION__); var_dump(__CLASS__); var_du

  • Spring的编程式事务和声明式事务详解

    入口(了解一些基本概念) Spring事务属性(事务的属性有哪些?) 我们都知道事务有开始,保存点,提交,回滚,隔离级别等属性.那么Spring对于事务属性定义有哪些呢?通过TransactionDefinition接口我们可以了解到: public interface TransactionDefinition{ int getIsolationLevel(); int getPropagationBehavior(); int getTimeout(); boolean isReadOnly

  • apache zookeeper使用方法实例详解

    本文涉及了Apache Zookeeper使用方法实例详解的相关知识,接下来我们就看看具体内容. 简介 Apache Zookeeper 是由 Apache Hadoop 的 Zookeeper 子项目发展而来,现在已经成为了 Apache 的顶级项目.Zookeeper 为分布式系统提供了高效可靠且易于使用的协同服务,它可以为分布式应用提供相当多的服务,诸如统一命名服务,配置管理,状态同步和组服务等. Zookeeper 接口简单,开发人员不必过多地纠结在分布式系统编程难于处理的同步和一致性问

  • Spring boot jpa 删除数据和事务管理的问题实例详解

    今天我们介绍的是jpa删除和事务的一些坑,接下来看看具体内容. 业务场景(这是一个在线考试系统)和代码:根据问题的id删除答案 repository层: int deleteByQuestionId(Integer questionId); service 层: public void deleteChoiceAnswerByQuestionId(Integer questionId) { choiceAnswerRepository.deleteByQuestionId(questionId)

  • Java8 新特性Lambda表达式实例详解

    Java8 新特性Lambda表达式实例详解 在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(View v); } 我们是这样使用它的: button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setText("

随机推荐