使用 IdentityServer 4 时如何在 Api 项目中添加额外声明

How to add additional claims in Api Project when using IdentityServer 4

对不起我的英语。

我有三个项目:IdentityServer、Ensino.Mvc、Ensino.Api。 IdentityServer 项目提供了主要的身份信息和声明 - claim Profile、claim Address、claim Sid...等,来自 IdentityServer4 库。 Ensino.Mvc 项目在令牌中获取此信息并将其发送到 API,以便授予 MVC 对资源的访问权限。令牌包含 IdentityServer 提供的所有声明。但是在 API 中,我需要生成其他 API 特定的声明,例如:声明 EnrollmentId 对应于令牌中的声明 Sid。而且我还想在 HttpContext 中添加此声明以供将来使用。有人可以告诉我如何实现吗?

我在 Startup.ConfigureServices 中有此代码:

// Add identity services
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5100";
                options.RequireHttpsMetadata = false;
                options.ApiName = "beehouse.scope.ensino-api";
            });

        // Add mvc services
        services.AddMvc();

在其他项目中,没有 API,只有 mvc,我继承了 UserClaimsPrincipalFactory 并覆盖了 CreateAsync 以添加额外的声明。我喜欢做这样的事情,但在 API 项目中。可能吗?

执行此操作的最佳方法是什么?

编辑:经过一些研究,我想做的是:根据声明和特定的 api 数据库数据,通过 IdentityServer 进行身份验证并在 api 中设置授权。

好的,一步一步来:

  1. 您需要在 Identity Server 中创建一个 API 资源(在您的情况下为 beehouse.scope.ensino-api,但我建议您在此处发布代码时隐藏此类信息)。它应该与您的 options.ApiName
  2. 同名
  3. 您需要将此范围添加到您的 MVC 客户端的允许范围。

对这两个步骤进行了描述 here,但主要是在添加资源时,您可以执行以下操作:

new ApiResource("beehouse.scope.ensino-api", "My test resource", new List<string>() { "claim1", "claim2" });

然后在您的客户端配置中:

new Client
    {
        ClientId = "client",
        .
        .
        // scopes that client has access to
        AllowedScopes = { "beehouse.scope.ensino-api" }
        .
        .
    }

这会将与此资源关联的声明添加到令牌中。 当然,您必须在 Identity Server 级别设置此声明,但根据您所说的,您已经知道如何执行此操作。

在您的 API 项目中,您可以将自己的事件处理程序添加到 options.JwtBearerEvents.OnTokenValidated。这是 ClaimsPrincipal 已设置的位置,您可以向身份添加声明或向主体添加新身份。

services.AddAuthentication("Bearer")
   .AddIdentityServerAuthentication(options =>
   {
       options.Authority = "http://localhost:5100";
       options.RequireHttpsMetadata = false;
       options.ApiName = "beehouse.scope.ensino-api";

       options.JwtBearerEvents.OnTokenValidated = async (context) => 
       {
           var identity = context.Principal.Identity as ClaimsIdentity;

           // load user specific data from database
           ...

           // add claims to the identity
           identity.AddClaim(new Claim("Type", "Value"));
       };
   });

请注意,这将 运行 对 API 的每个请求进行处理,因此如果您从数据库加载信息,最好缓存声明。

另外,Identity Server 应该只负责识别用户,而不是他们做什么。他们所做的是特定于应用程序的(角色、权限等),因此您认识到这一点并避免与 Identity Server 的逻辑交叉是正确的。

使用 IdentityServerAuthenticationHandler 制作自己的 AuthenticationHandler 将是最佳选择。这将允许您使用 DI、拒绝身份验证并在不需要时跳过自定义身份验证处理程序。

首先验证令牌然后添加更多声明的示例AuthenticationHandler

public class MyApiAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Pass authentication to IdentityServerAuthenticationHandler
        var authenticateResult = await Context.AuthenticateAsync("Bearer");

        // If token authentication fails, return immediately
        if (!authenticateResult.Succeeded)
        {
            return authenticateResult;
        }

        // Get user ID from token
        var userId = authenticateResult.Principal.Claims
            .FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value;

        // Do additional checks for authentication
        // e.g. lookup user ID in database
        if (userId == null)
        {
            return AuthenticateResult.NoResult();
        }

        // Add additional claims
        var identity = authenticateResult.Principal.Identity as ClaimsIdentity;
        identity.AddClaim(new Claim("MyClaim", "MyValue"));

        return authenticateResult;
    }
}

向服务添加处理程序:

services.AddAuthentication()
    .AddIdentityServerAuthentication(options =>
    {
        // ...
    })
    .AddScheme<AuthenticationSchemeOptions, MyApiAuthenticationHandler>("MyApiScheme", null);

现在您可以使用任一方案:

// Authenticate token and get extra API claims
[Authorize(AuthenticationSchemes = "MyApiScheme")]

// Authenticate just the token
[Authorize(AuthenticationSchemes = "Bearer")]

注意 IdentityServerAuthenticationHandler 做同样的事情,using the dotnet JWT handler:

public class IdentityServerAuthenticationHandler : AuthenticationHandler<IdentityServerAuthenticationOptions>
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        ...
        return await Context.AuthenticateAsync(jwtScheme);
        ...
    }
}