如何在由Identity Server 4保护的webapi中使自定义声明可访问?

问题描述

我有一个由IdentityServer4保护的asp.net核心mvc Web应用程序和一个asp.net核心api项目。 当用户登录(到mvc应用程序)时,我添加一个声明(“ mycompany”),我希望可以在api项目中对其进行访问。

我可以访问mvc项目中的附加声明,但是不能访问api项目中的新声明。

如何使声明(“ mycompany”)显示在api项目中?

MVC App Startup.cs

services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "Cookies";
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie("Cookies").AddOpenIdConnect("oidc",options =>
                {
                    options.Authority = Configuration.GetSection("IdpConfig").GetValue<string>("IdpUri");
                    options.RequireHttpsMetadata = false;

                    options.ClientId = "MyProject.Web";
                    options.ClientSecret = "SomeSecret";
                    options.ResponseType = "code id_token";
                    options.Savetokens = true;

                    options.Events = new OpenIdConnectEvents
                    {
                        OnTicketReceived = async (context) =>
                        {
                            try
                            {
                                var claimsIdentity = context.Principal.Identity as ClaimsIdentity;

                                claimsIdentity.AddClaim(new Claim("mycompany","some company"));

                                await Task.Fromresult(0);
                            }
                            catch (Exception ex)
                            {
                                // Log This
                            }
                        },OnUserinformationReceived = (context) => Task.Fromresult(0),OnTokenValidated = (context) =>
                        {
                            return Task.Fromresult(0);
                        },OnRedirectToIdentityProvider = (context) => Task.CompletedTask
                    };
                });

在MVC应用程序中,此方法有效(返回“某家公司”),但是在api中,该声明不存在

public static string GetCompany(this ClaimsPrincipal principal)
{
    var company = principal.Claims.FirstOrDefault(c => c.Type == "mycompany");
    return company?.Value;
}

解决方法

您在MVC应用程序中添加的声明已添加到本地身份,您需要将新声明添加到发送到API的访问令牌中。您可以将该声明添加到IdentitySever中的APIScope / APIResources中。您无法在MVC应用程序中更改访问权限,因为令牌的签名将不再正确。

,

我认为它起作用的方式

在身份服务器上,您可以为用户添加新的Claim(AspNetUserClaims表),并为其指定公司名称和值TEST COMPANY。 在IdentityClaims表内添加声明公司,并将该记录引用到配置文件范围(IdentityResources表)的IdentityResourceId中,以便当您的客户端应用要求配置文件范围IS时,将使用公司声明及其值填充AccessToken。

配置MVC客户端以映射自定义声明并能够询问所需的作用域

options.ClaimActions.MapUniqueJsonKey("MyCompany","company");

//two trips to load claims into the cookie
//but the id token is smaller
options.GetClaimsFromUserInfoEndpoint = true;

//configure scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");

如果您获得访问令牌并在JWT.IO上进行了检查,则应包含具有其价值的自定义声明公司。

var accessToken = await HttpContext.GetTokenAsync("access_token");

在您的MVC客户中,您可以获得这样的公司名称:

var companyName = User.Claims.Where(x => x.Type == "MyCompany").FirstOrDefault()?.Value;
,

要访问API项目中的自定义声明,您需要在JWT访问令牌中包括这些声明。对于IdentityServer4,您需要实现IProfileService接口。

例如

public class UserProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // Ensure adding claims to access token only
        if (context.Caller.Equals("ClaimsProviderAccessToken",StringComparison.OrdinalIgnoreCase))
        {
            var claims = new List<Claim>();

            claims.Add(new Claim("<CUSTOM_CLAIM_TYPE>","value",ClaimValueTypes.String));
            
            context.RequestedClaimTypes = new[] { "<CUSTOM_CLAIM_TYPE>" };

            context.AddRequestedClaims(claims);
        }

        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;

        return Task.CompletedTask;
    }
}

您还可以使用IUserClaimsPrincipalFactory向身份添加自定义声明,然后通过GetProfileDataAsync()RequestedClaimTypes方法中对其进行过滤。

然后在ConfigureServices()类的Startup方法中,向UserProfileService注册IIdentityServerBuilder

services.AddIdentityServer(...)
    .AddProfileService<UserProfileService>();

现在,您的访问令牌具有自定义声明,您可以在API项目中访问该声明,例如,通过控制器的User属性。

正如评论中已经提到的那样,建议尽可能减少访问令牌,仅在需要时根据访问令牌中的用户ID获取自定义声明。 >