Identity Server 4返回自定义用户Claim

Identity Server 4返回自定义用户Claim

 

Identity Server系列目录

  1. Blazor Server访问Identity Server 4单点登录 - SunnyTrudeau - 博客园 (cnblogs.com)
  2. Blazor Server访问Identity Server 4单点登录2-集成Asp.Net角色 - SunnyTrudeau - 博客园 (cnblogs.com)
  3. Blazor Server访问Identity Server 4-手机验证码登录 - SunnyTrudeau - 博客园 (cnblogs.com)
  4. Blazor MAUI客户端访问Identity Server登录 - SunnyTrudeau - 博客园 (cnblogs.com)
  5. 在Identity Server 4项目集成Blazor组件 - SunnyTrudeau - 博客园 (cnblogs.com)
  6. Identity Server 4退出登录自动跳转返回 - SunnyTrudeau - 博客园 (cnblogs.com)
  7. Identity Server通过ProfileService返回用户角色 - SunnyTrudeau - 博客园 (cnblogs.com)

 

上一篇提到,尝试通过ProfileService返回更多用户声明,有的可以,有的不行。这个问题其实可以通过认证服务器的ProfileService 和客户端项目AddOpenIdConnect的options.ClaimActions.MapJsonKey配置解决。官网有提到ClaimActions.MapJsonKey:

在 ASP.NET Core 中保存外部提供程序的其他声明和令牌 | Microsoft Docs

options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");

options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");

 

修改Identity Server 4认证服务器项目

打开AspNetId4Web认证服务器项目,先升级用户实体类,增加一个字段:民族。

public class ApplicationUser : IdentityUser

    {

        /// <summary>

        /// 民族

        /// </summary>

        public string Nation { get; set; }

    }

 

生成一个数据库的迁移,打开程序包管理器控制台:

PM> Add-Migration nation

PM> Update-Database

 

在种子数据初始化时给alice和bob增加民族。

                        alice.Nation = "汉族";

                        bob.Nation = "壮族";

 

在ProfileService中,把民族添加到用户claims,注意,要从数据库中查询用户,获取Nation字段,context.Subject里边是没有的。

public class ProfileService : IProfileService
    {
        private readonly IServiceProvider _serviceProvider;

        public ProfileService(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            using var scope = _serviceProvider.CreateScope();
            var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            //按Name找不到
            //var user = await userMgr.FindByNameAsync(context.Subject.Identity.Name);
            //按Sub找得到
            string userId = context.Subject.FindFirstValue(JwtClaimTypes.Subject);
            var user = await userMgr.FindByIdAsync(userId);

            var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
            context.IssuedClaims.AddRange(nameClaim);

            var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
            context.IssuedClaims.AddRange(roleClaims);

            var emailClaims = context.Subject.FindAll(JwtClaimTypes.Email);
            context.IssuedClaims.AddRange(emailClaims);

            var phoneNumberClaims = context.Subject.FindAll(JwtClaimTypes.PhoneNumber);
            context.IssuedClaims.AddRange(phoneNumberClaims);

            //获取民族字段
            var nationClaim = new Claim("nation", user.Nation);
            context.IssuedClaims.Add(nationClaim);

            await Task.CompletedTask;
        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            await Task.CompletedTask;
        }

 

修改Blazor Server客户端项目

在BlzOidc项目中,配置oidc参数时,添加映射的字段nation,上一篇测试发现PhoneNumber字段无法返回,添加映射也可以解决。

 

//添加认证相关的服务
    private static void ConfigureAuthServices(IServiceCollection services)
    {
        //清除微软定义的clamis
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        //默认采用cookie认证方案,添加oidc认证方案
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "cookies";
            options.DefaultChallengeScheme = "oidc";
        })
            //配置cookie认证
            .AddCookie("cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                //id4服务的地址
                options.Authority = "https://localhost:5001";

                //id4配置的ClientId以及ClientSecrets
                options.ClientId = "BlazorServerOidc";
                options.ClientSecret = "BlazorServerOidc.Secret";

                //认证模式
                options.ResponseType = "code";

                //保存token到本地
                options.SaveTokens = true;

                //很重要,指定从Identity Server的UserInfo地址来取Claim
                //效果等同id4配置AlwaysIncludeUserClaimsInIdToken = true
                options.GetClaimsFromUserInfoEndpoint = true;

                //指定要取哪些资料(除Profile之外,Profile是默认包含的)
                options.Scope.Add("scope1");
                //通过ProfileService返回用户角色
                //options.Scope.Add("role");

                //映射自定义用户声明
                options.ClaimActions.MapJsonKey(JwtClaimTypes.PhoneNumber, JwtClaimTypes.PhoneNumber);
                options.ClaimActions.MapJsonKey("nation", "nation");

                //这里是个ClaimType的转换,Identity Server的ClaimType和Blazor中间件使用的名称有区别,需要统一。
                //User.Identity.Name=JwtClaimTypes.Name
                options.TokenValidationParameters.NameClaimType = "name";
                options.TokenValidationParameters.RoleClaimType = "role";

                options.Events.OnUserInformationReceived = (context) =>
                {
                    //id4返回的角色是字符串数组或者字符串,blazor server的角色是字符串,需要转换,不然无法获取到角色
                    ClaimsIdentity claimsId = context.Principal.Identity as ClaimsIdentity;

                    var roleElement = context.User.RootElement.GetProperty(JwtClaimTypes.Role);
                    if (roleElement.ValueKind == System.Text.Json.JsonValueKind.Array)
                    {
                        var roles = roleElement.EnumerateArray().Select(e => e.ToString());
                        claimsId.AddClaims(roles.Select(r => new Claim(JwtClaimTypes.Role, r)));
                    }
                    else
                    {
                        claimsId.AddClaim(new Claim(JwtClaimTypes.Role, roleElement.ToString()));
                    }

                    return Task.CompletedTask;
                };
            });

    }

 

VS2022调试运行AspNetId4Web认证服务器项目和BlzOidc项目,在BlzOidc主页点击登录,跳转到AspNetId4Web认证服务器,输入手机验证码登录,跳转回到BlzOidc主页,可以看到,想要的用户信息都有了

Identity Server 4返回自定义用户Claim

 

DEMO代码地址:https://gitee.com/woodsun/blzid4

 

上一篇:Identity Server 4 从入门到落地(六)—— 简单的单页面客户端


下一篇:Identity Server通过ProfileService返回用户角色