如何使用带有IdentityServer4的ASP.Net Identity添加要包含在access_token中的其他声明 [英] How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4

查看:120
本文介绍了如何使用带有IdentityServer4的ASP.Net Identity添加要包含在access_token中的其他声明的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何添加要包含在令牌中的其他声明?

How to add additional claims to be included within the token?

API收到承载令牌后,将使用以下声明填充User.Identity对象.

As soon as the API receives the bearer token, the User.Identity object gets populated with the following claims.

[
  {
    "key": "nbf",
    "value": "1484614344"
  },
  {
    "key": "exp",
    "value": "1484615244"
  },
  {
    "key": "iss",
    "value": "http://localhost:85"
  },
  {
    "key": "aud",
    "value": "http://localhost:85/resources"
  },
  {
    "key": "aud",
    "value": "WebAPI"
  },
  {
    "key": "client_id",
    "value": "MyClient"
  },
  {
    "key": "sub",
    "value": "d74c815a-7ed3-4671-b4e4-faceb0854bf6"
  },
  {
    "key": "auth_time",
    "value": "1484611732"
  },
  {
    "key": "idp",
    "value": "local"
  },
  {
    "key": "role",
    "value": "AccountsManager"
  },
  {
    "key": "scope",
    "value": "openid"
  },
  {
    "key": "scope",
    "value": "profile"
  },
  {
    "key": "scope",
    "value": "roles"
  },
  {
    "key": "scope",
    "value": "WebAPI"
  },
  {
    "key": "scope",
    "value": "offline_access"
  },
  {
    "key": "amr",
    "value": "pwd"
  }
]

我想要其他声明,例如username, email, legacySystemUserId等.这些字段已经存在于AspNetUsers表中(并且不再重复存在于AspNetUserClaims表中),并且可以在ASP .Net Core应用程序中的ApplicationUser对象中使用

I want additional claims like username, email, legacySystemUserId, etc. These fields already exist in the AspNetUsers table (and doesn't repetitively exist in AspNetUserClaims table) and are available in ASP .Net Core application in my ApplicationUser object.

我希望将它们包含在使用用户名和密码进行身份验证后返回的访问令牌中.想要在我的WebAPI应用程序中使用无法访问身份服务器数据库的控件,并且其自己的数据库中存储的数据基于用户的电子邮件地址而不是UserId(这是ASP .NET Identity中生成的GUID,以SUB索赔).

I want them to be included in access token that is returned after authenticating with username and password. Want to use the same in my WebAPI application that doesn't have access to the identity-server database and its own database has data stored based on user's email address not the UserId (which is a guid generated in ASP .NET Identity and received as SUB claim).

推荐答案

我一直在同一个问题上斗争了好几个小时,最后才将解决方案拼凑在一起.此文章是一个很大的帮助,但总结并分享我的实现:

I had been fighting this same issue for hours and finally pieced together the solution. This article was a big help, but to summarize and share my implementation:

为了获得分配给用户的声明并将它们附加到访问令牌,您需要在身份服务器上实现两个接口:IResourceOwnerPasswordValidatorIProfileService.以下是我对这两个类的实现,虽然是草稿,但它们可以工作.

In order to get the claims assigned to the user and attach them to the access token, you need to implement two interfaces on the identity server: IResourceOwnerPasswordValidator and IProfileService. The following are my implementations of the two classes and are rough drafts, but they work.

**请确保当前获得最新版本的IdentityServer4-1.0.2.

**Be sure to get the latest version of IdentityServer4 - 1.0.2 at this time.

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中的服务中:

Once you have those, they need to be added to your services in startup.cs:

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

这里是我的配置的快速浏览:

Here is a quick look at my config:

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId()
    };
}

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource
        {
            Name = "api1",
            Description = "My Api",
            Scopes =
            {
                new Scope()
                {
                    Name = "api1",
                    DisplayName = "Full access to Api"
                }
            }
        }
    };
}

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "apiClient",
            ClientName = "Api Angular2 Client",
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
            AlwaysSendClientClaims = true,
            AlwaysIncludeUserClaimsInIdToken = true,
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            AllowedScopes =
            {
                "api1"
            }
        }
    };
}

之后,从客户端调用身份服务器:

After that, a call to the identity server from a client:

var discoTask = DiscoveryClient.GetAsync("http://localhost:5000");
var disco = discoTask.Result;

var tokenClient = new TokenClient(disco.TokenEndpoint, "apiClient", "secret");
var tokenResponseTask = tokenClient.RequestResourceOwnerPasswordAsync("user@domain.com", "my-password", "api1");

var tokenResponse = tokenResponseTask.Result;
var accessToken = tokenResponse.AccessToken;

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

在jwt.io上检查令牌并查看结果...

Inspect the token at jwt.io and see your results...

这篇关于如何使用带有IdentityServer4的ASP.Net Identity添加要包含在access_token中的其他声明的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆