【问题标题】:How to add additional claims in Api Project when using IdentityServer 4使用 IdentityServer 4 时如何在 Api 项目中添加其他声明
【发布时间】:2018-10-15 22:35:08
【问题描述】:

对不起我的英语。

我有三个项目:IdentityServer、Ensino.Mvc、Ensino.Api。 IdentityServer 项目从 IdentityServer4 库中提供主要的身份信息和声明 - 声明配置文件、声明地址、声明 Sid 等。 Ensino.Mvc 项目在令牌中获取此信息并将其发送到 API,以便 MVC 被授予对资源的访问权限。令牌包含 IdentityServer 提供的所有声明。但是在 API 中,我需要生成其他特定于 API 的声明,例如:与令牌中的声明 Sid 对应的声明 EnrollmentId。而且我想在 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 项目中做这样的事情。有可能吗?

最好的方法是什么?

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

【问题讨论】:

  • 在 API in 中生成声明是什么意思?声明是在 IdentityServer 上生成的,并且基于正在验证的用户/客户端。对我来说,您似乎需要一个 API 所需的范围,并且该范围应包含您的附加声明。我说的对吗?
  • @m3n7alsnak3,我想是的。我的 Ensino.Api 创建了一个“学校”,我应该用特定的 ID 给我的用户 SchoolPrincipal 打电话。 IdentityServer 只知道个人资料,不知道学校。因此,在 Ensino.Api 中,我必须将声明 SchoolPrincipal 添加到身份信息中。
  • 我对你有基本相同的需求,通过扩展 IdentityServer4 API,比如创建一个新的端点,你可以通过从你的 API 发出一个 post 请求,在 IdentityServer 本身中做你需要的事情。为此,解决方案已启用:Custom endpoint for authorized clients on Identity Server 4 如需补充,请查看此链接:IdentityServer4 Adding more API Endpoints doc

标签: asp.net-mvc asp.net-core asp.net-identity identityserver4 claims-based-identity


【解决方案1】:

在您的 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 的逻辑交叉方面是正确的。

【讨论】:

  • 这正是我想要的。谢谢!
【解决方案2】:

使用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);
        ...
    }
}

【讨论】:

    【解决方案3】:

    好的,一步一步来:

    1. 您需要在 Identity Server 中创建一个 API 资源(在您的情况下为beehouse.scope.ensino-api,但我建议您在此处发布代码时隐藏此类信息)。它应该与您的options.ApiName 同名
    2. 您需要将此范围添加到 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 级别设置此声明,但根据您所说的,您已经知道如何执行此操作。

    【讨论】:

    • 这样我必须在 Identity Server 中添加声明。这正是我试图避免的。我不知道我的方法是否是糟糕的架构,但我认为以这种方式解决:Api 接收令牌。 Api 从令牌中获取 sid。 Api 转到数据库并尝试获取此 sid 的学校。如果有学校 ID,Api 添加“SchoolPrincipal”声明。然后 Api 转到数据库并尝试获取 Enrollments。如果有注册,请添加“学生”声明。我试图阻止 IdentityServer 知道这一点。 Identity Server 无权访问 School 数据库。并且 Api 不访问身份服务器数据库。
    猜你喜欢
    • 1970-01-01
    • 2017-12-14
    • 2020-04-12
    • 2016-09-26
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多