最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

IdentityServer4 - v4.x .Net中的实践应用

来源:博客园

认证授权服务的创建

以下内容以密码授权方式为例。

创建模拟访问DB各数据源类

为模拟测试准备的数据源。

/// 假设的用户模型public class TestUser{    public string id { get; set; } = string.Empty;    public string username { get; set; } = string.Empty;    public string password { get; set; } = string.Empty;    public string nickname { get; set; } = string.Empty;    public string gender { get; set; } = string.Empty;    public string email { get; set; } = string.Empty;    public string phone { get; set; } = string.Empty;    public string address { get; set; } = string.Empty;}/// 假设的DB数据public class DB{/// Scope数据源方法(4.x 时 很重要!!!)public static IEnumerable ApiScopes => new ApiScope[]{new ApiScope("add","新增"),new ApiScope("search","查询"),new ApiScope("shopping","购物"),};        /// ApiResource 数据源方法/// 需要被认证授权的资源(服务站点)数据源public static IEnumerable GetApiResources => new ApiResource[]{    new ApiResource("user", "会员服务")    {        // v4.x 时 很重要!!!        Scopes = { "add", "search" },                // 指定资源中,可取得的身份(用户)信息        UserClaims={ JwtClaimTypes.NickName }    },    new ApiResource("product", "产品服务")            {                Scopes = { "add", "shopping" },                UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.NickName, "email", "depart", "role"}            },    new ApiResource("order", "订单服务")            {                Scopes = { "add", "shopping"},                UserClaims = { JwtClaimTypes.Gender, "zip" }            }};/// 身份资源配置数据源方法public static IEnumerable IdentityResources => new IdentityResource[]{// 必须项new IdentityResources.OpenId(),new IdentityResources.Profile(),// 扩展项new IdentityResources.Email(),new IdentityResources.Phone(),new IdentityResources.Address(),// 自定义追加项new IdentityResource("org",new string[]{"depart","role"}),new IdentityResource("zip",new string[]{"zip"})};/// 客户端数据源方法public static IEnumerable Clients => new Client[]{    new Client    {        ClientId = "Cli-c",        ClientName="客户端-C-密码方式认证",        AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,        ClientSecrets = { new Secret("secret_code".Sha256()) },        // 支持token过期后自动刷新token,增强体验        AllowOfflineAccess = true,        AccessTokenLifetime = 360000,        AllowedScopes = { "add", "search", "shopping",            IdentityServerConstants.StandardScopes.OpenId,            IdentityServerConstants.StandardScopes.Profile,            JwtClaimTypes.Email, "org","zip",            IdentityServerConstants.StandardScopes.OfflineAccess        }    }};/// 用户数据源方法public static IEnumerable Users => new TestUser[] {    new TestUser{        id = "10001", username = "sol", password = "123", nickname = "Sol",                email = "sol@domain.com", phone="13888888888", gender = "男", address="jingan"    },    new TestUser{        id = "10002", username = "song", password = "123", nickname = "Song",                email = "song@domain.com", phone="13888888888", gender = "女", address="jingan"    }};/// 用户是否激活方法public static bool GetUserActive(string userid){    return Users.Any(a => a.id == userid);}}

为 Client 实现 IClientStore 接口

/// 客户端数据查询public class ClientStore : IClientStore{        // 客户端验证方法public Task FindClientByIdAsync(string clientId){// 数据库查询 Client 信息var client = DB.Clients.FirstOrDefault(c => c.ClientId == clientId) ?? new Client();client.AccessTokenLifetime = 36000;return Task.FromResult(client);}}

为 ApiResource 实现 IResourceStore 接口

从中可以理出 IdentityResource、ApiResource、ApiScope 三者的关系。


(资料图)

/// /// 各个资源数据的查询方法/// 包括:IdentityResource、ApiResource、ApiScope 三项资源/// public class ResourceStore : IResourceStore{    public Task FindApiResourcesByNameAsync(IEnumerable apiResourceNames)    {        if (apiResourceNames == null) throw new ArgumentNullException(nameof(apiResourceNames));        var result = DB.GetApiResources.Where(r => apiResourceNames.Contains(r.Name));        return Task.FromResult(result);    }    public Task FindApiResourcesByScopeNameAsync(IEnumerable scopeNames)    {        if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));        var result = DB.GetApiResources.Where(t => t.Scopes.Any(item => scopeNames.Contains(item)));        return Task.FromResult(result);    }    public Task FindApiScopesByNameAsync(IEnumerable scopeNames)    {        if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));        var result = DB.ApiScopes.Where(w => scopeNames.Contains(w.Name));        return Task.FromResult(result);    }    public Task FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames)    {        if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));        var result = DB.IdentityResources.Where(w => scopeNames.Contains(w.Name));        return Task.FromResult(result);    }    public Task GetAllResourcesAsync()    {        return Task.FromResult(new Resources(DB.IdentityResources, DB.GetApiResources, DB.ApiScopes));    }}

用户信息 Profile 的接口实现

/// /// 认证通过的用户资料信息 的处理,后续公布到Token中/// public class UserProfileService : IProfileService{    public Task GetProfileDataAsync(ProfileDataRequestContext context)    {        // 把需要公开到Token中的用户claim信息,放到指定的IssuedClaims中,为后续生成 Token 所用        var userid = context.Subject.GetSubjectId();        if (userid != null)        {            var claims = context.Subject.Claims.ToList();            // 此方法,会依据Client请求的Scope(Claim),过滤Claim后的集合放入到 IssuedClaims 中            context.AddRequestedClaims(claims);            // 不按 Client.Scope 的过滤,所有的用户claim全部放入(不推荐)            // context.IssuedClaims = claims.ToList();        }        return Task.CompletedTask;    }    public Task IsActiveAsync(IsActiveContext context)    {        string userid = context.Subject.GetSubjectId();        // 查询 DB,ids4需要知道 用户是否已激活        context.IsActive = DB.GetUserActive(userid);        return Task.CompletedTask;    }}

密码方式验证用户,实现 IResourceOwnerPasswordValidator 接口

/// /// 密码方式认证过程/// public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator{    ///     /// 1、验证 用户是否合法    /// 2、设定 身份基本信息    /// 3、设定 返回给调用者的 Response 结果信息    ///     ///     ///     public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)    {        try        {            //验证用户,用户名和密码是否正确            var user = DB.Users.FirstOrDefault(u => u.username == context.UserName && u.password == context.Password);            if (user != null)            {                #region 设置 身份(用户)基本信息                // 身份信息的相关属性,带入到ids4中                var claimList = new List()                {                    // Claim 多(自定义)属性                    new Claim(JwtClaimTypes.Name,user.username),                    new Claim(JwtClaimTypes.NickName,user.nickname),                    new Claim(JwtClaimTypes.Email,user.email),                    new Claim(JwtClaimTypes.Gender,user.gender),                    new Claim(JwtClaimTypes.PhoneNumber,user.phone),                    new Claim("zip","200000")                };                                // 追加Claim自定义用户属性                string[] roles = new string[] { "SupperManage", "manage", "admin", "member" };                string[] departs = new string[] { "销售部", "人事部", "总经理办公室" };                foreach (var rolename in roles)                {                    claimList.Add(new Claim(JwtClaimTypes.Role, rolename));                }                foreach (var departname in departs)                {                    claimList.Add(new Claim("depart", departname));                }                #endregion                #region 设置 返回给调用者的Response信息                // 在以下 GrantValidationResult 类中                // 1、通过以上已组装的 ClaimList,再追加上系统必须的Claim项,组装成最终的Claims                // 2、用 Claims ==> 创建出 ClaimsIdentity ==> 再创建出 ClaimsPrincipal                // 以完成 Response 的 json 结果 返回给 调用者                context.Result = new GrantValidationResult(                    subject: user.id,                    claims: claimList,                    authenticationMethod: "db_pwdmode",                    // Response 的 json 自定义追加项                    customResponse: new Dictionary {                        { "custom_append_author", "认证授权请求的Response自定义追加效果" },                        { "custom_append_discription", "认证授权请求的Response自定义追加效果" }                    }                );                #endregion            }            else if (user == null)            {                context.Result = new GrantValidationResult(                    TokenRequestErrors.InvalidGrant,                    "用户认证失败,账号或密码不存在;无效的自定义证书。"                );            }        }        catch (Exception ex)        {            context.Result = new GrantValidationResult()            {                IsError = true,                Error = ex.Message            };        }        return Task.CompletedTask;    }}

认证授权服务配置

public class Program{    public static void Main(string[] args)    {        var builder = WebApplication.CreateBuilder(args);        builder.Services.AddControllers();        #region IdentityServer 的配置        builder.Services.AddIdentityServer()            // 支持开发环境的签名证书            .AddDeveloperSigningCredential()            // 分别注册各自接口的实现类            .AddResourceStore().AddClientStore().AddResourceOwnerValidator().AddProfileService();            // 可追加的扩展            //.AddExtensionGrantValidator<微信自定义扩展模式>();        #endregion        builder.Services.AddEndpointsApiExplorer();        builder.Services.AddSwaggerGen();        var app = builder.Build();        if (app.Environment.IsDevelopment())        {            app.UseSwagger();            app.UseSwaggerUI();        }        app.UseRouting();                #region 使用 ids4 服务        // 它需要在 [路由] 之后,[授权] 之前。        app.UseIdentityServer();        app.UseAuthorization();        #endregion        app.UseEndpoints(endpoints =>        {            endpoints.MapControllers();        });        app.Run();    }}

认证授权服务请求效果

从上图看出:用户密码验证成功、客户端密钥Secret验证成功。

这里重点解释下Scope:

Client参数Scope中包含了: Scope(shopping) + UserClaim(openid+profile+org+email)

数据源DB类 ApiResource 中的产品服务、订单服务都包含了shopping,所以access_token可以访问这两个服务。

数据源DB类 Client、IdentityResource 中已定义了 openid+profile+org+email,所以access_token中包含了此几种用户信息。

认证授权服务 /connect/userinfo 取得的身份信息图例:

上图显示结果:Client.Scope匹配到的 ApiResources.UserClaims 合并的结果

解析Token数据图例:

上图显示:

aud:已授权的(Client.Scope匹配到的)ApiResource服务名称集合(product/order)

name/nickname/email/role/...Claims数据:已授权服务(product/order)UserClaims的合并结果

client_id:申请的客户端标识

nbf/exp:认证授权时间/token过期时间

Token访问授权服务

创建一个API产品服务,配置产品服务

var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();#region Authentication 授权认证builder.Services.AddAuthorization();builder.Services.AddAuthentication(options =>{    // 数据格式设定,以 IdentityServer 风格为准    options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;    options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;    options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;    options.DefaultForbidScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;    options.DefaultSignInScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;    options.DefaultSignOutScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;}).AddIdentityServerAuthentication(options =>{    options.Authority = "http://localhost:5007";    // IdentityServer 授权服务地址    options.RequireHttpsMetadata = false;           // 不需要https    options.ApiName = "product";                    // 当前服务名称(与认证授权服务中 ApiResources 的名称对应)});#endregionbuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();var app = builder.Build();if (app.Environment.IsDevelopment()){    app.UseSwagger();    app.UseSwaggerUI();}app.UseRouting();#region IdentityServer4 注册// 放在路由之后,授权之前app.UseAuthentication();app.UseAuthorization();#endregionapp.MapControllers();app.Run();

API服务中设定必须授权的Action:

/// 获取当前身份信息[HttpGet, Authorize(Roles = "SupperManage")]public IEnumerable Get(){    /// 授权后的身份(用户)信息(从Token中提取的用户属性信息)    var Principal = HttpContext.User;        /// 返回 获取到的身份(用户)信息    return new List { new    {        UserId = Principal.Claims.FirstOrDefault(oo => oo.Type == "sub")?.Value,        UserName = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.Name)?.Value,        NickName = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.NickName)?.Value,        Email = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.Email)?.Value    }};}

取得当前身份(用户)信息效果图:

关键词: 基本信息 验证方法 自动刷新