ASP.Net Core下转换Open Id Connect声明

14
我正在编写一个ASP.Net Core Web应用程序,并使用UseOpenIdConnectAuthentication将其连接到IdentityServer3。模仿他们的ASP.Net MVC 5示例,我试图转换从Identity Server返回的声明以删除“肯定不需要的低级协议声明”。在MVC 5中,他们为SecurityTokenValidated通知添加了处理程序,将AuthenticationTicket替换为仅具有所需声明的内容。
在ASP.Net Core中,要实现相同的功能,我认为需要处理OpenIdConnectEvents中的OnTokenValidated。然而,在该阶段似乎还没有检索到其他范围信息。如果我处理OnUserInformationReceived,则会出现额外的信息,但存储在用户而不是主体上。
其他事件似乎都不是永久删除我不感兴趣的声明的明显位置。任何建议都将不胜感激!
5个回答

12

我喜欢LeastPrivilege的建议,在流程中尽早进行转换。 提供的代码并不完全有效。 这个版本可以:

var oidcOptions = new OpenIdConnectOptions
{
   ...

   Events = new OpenIdConnectEvents
   {
       OnTicketReceived = e =>
       {
          e.Principal = TransformClaims(e.Ticket.Principal);
          return Task.CompletedTask;
       }
   }
};

这将替换Principal而不是Ticket。你可以使用我另一个答案中的代码来创建新的Principal。你也可以同时替换Ticket,但我不确定是否必要。

因此,感谢LeastPrivilege和Adem提出的方式几乎回答了我的问题...只需要稍微调整一下代码即可。总体而言,我更喜欢LeastPrivilege早期转换声明的建议。


6
您可以实现 SignInSchemeOnSigningIn 事件。以下是一个示例:
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationScheme = "OpenIdCookies",
            AutomaticAuthenticate = true,
            Events = new CookieAuthenticationEvents()
            {
                OnSigningIn = async (context) =>
                {
                    ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
                    identity.Claims = identity.Claims.Where(...);
                }
            }
        });

        var oidcOptions = new OpenIdConnectOptions
        {
            AuthenticationScheme = "oidc",
            SignInScheme = "OpenIdCookies"
        };

        //.. set other options

        app.UseOpenIdConnectAuthentication(oidcOptions); 

1
谢谢您把我引导到正确的轨道。唯一的问题是identity.Claim是只读属性。我已经添加了一个可行的解决方案,但不确定它是否是“正确”的方法。 - Piers Lawson

6

感谢Adem的回复...它解决了绝大部分的问题...唯一的问题在于identity.Claim是只读属性。不过我发现创建一个新的Principal确实有效:

Events = new CookieAuthenticationEvents()
{
    OnSigningIn = (context) =>
    {
        ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;

        var givenName = identity.FindFirst(Constants.ClaimTypes.GivenName);
        var familyName = identity.FindFirst(Constants.ClaimTypes.FamilyName);
        var sub = identity.FindFirst(Constants.ClaimTypes.Subject);

        var claimsToKeep = new List<Claim> {givenName, familyName, sub};

        var newIdentity = new ClaimsIdentity(claimsToKeep, identity.AuthenticationType);

        context.Principal = new ClaimsPrincipal(newIdentity);

        return Task.FromResult(0);
    }
}

我不确定这是否是正确的方法,但它似乎有效。

你是否得出结论,如果用“Principal”替换“Ticket”,是否可行? - Jonas

6
我个人倾向于在发生实际认证的中间件中进行索赔转换。
您可以使用OIDC中间件的OnTicketReceived事件来实现。
var oidcOptions = new OpenIdConnectOptions
{
   AuthenticationScheme = "oidc",
   SignInScheme = "cookies",

   Authority = Clients.Constants.BaseAddress,

   ClientId = "mvc.hybrid",
   ClientSecret = "secret",
   ResponseType = "code id_token",
   SaveTokens = true,

   TokenValidationParameters = new TokenValidationParameters
   {
      NameClaimType = JwtClaimTypes.Name,
      RoleClaimType = JwtClaimTypes.Role,
   },

   Events = new OpenIdConnectEvents
   {
       OnTicketReceived = e =>
       {
           ClaimsPrincipal p = TransformClaims(e.Ticket.Principal);
           e.Ticket = new AuthenticationTicket(
            p,
            e.Ticket.Properties,
            e.Ticket.AuthenticationScheme);

        return Task.CompletedTask;
    }
  }
};

这看起来应该可以工作,但是当我尝试时,原始声明仍然存在。如果您用新的Principal替换e.Principal而不是用新的AuthenticationTicket替换e.Ticket,则可以解决问题。 - Piers Lawson
@PiersLawson 你是否得出了任何结论,如果替换e.Ticket为e.Principal是否可行? - Jonas
1
这个答案对我很有帮助,使用AspNetCore 1.1.2进行身份验证并针对Identity Server 3进行认证。 - Mark

1

感谢本帖中的答案,我也成功地使它工作了。这里没有解决的问题是,如果需要修改要求服务的声明。在我的情况下,我需要构建服务提供程序以获取正确的依赖项以转换声明。(我还能够删除不需要转换的声明-这里也展示了代码)。

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddAuthentication(options =>
        {
            // set options
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
        {
            // set options
        })
        .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
        {
            // options such as Authority, ClientId, etc set here
            options.Authority = "your-value";
            options.ClientId = "your-value";
            // ...

            // remove automatically mapped claims we do not need, keeps the authentication cookie smaller
            options.ClaimActions.DeleteClaim("sid");
            options.ClaimActions.DeleteClaim("idp");
            options.ClaimActions.DeleteClaim("s_hash");
            options.ClaimActions.DeleteClaim("auth_time");

            options.Events.OnTicketReceived = async context =>
            {
                // Build the service provider and necessary dependencies
                // in order to enhance our claims once we receive it initially
                ServiceProvider serviceProvider = services.BuildServiceProvider();
                ICustomProvider customProvider = serviceProvider.GetService<ICustomProvider>();
                EnhanceClaimsTransformation claimsTransformation = new EnhanceClaimsTransformation(customProvider);

                context.Principal = await claimsTransformation.TransformAsync(context.Principal);
                await Task.CompletedTask;
            };
        });
}

EnhanceClaimsTransformation (注册在ConfigureServices中的ICustomProvider依赖注入)

请注意,在此代码中我们需要克隆原始Principal对象以向其添加声明。

public class EnhanceClaimsTransformation : IClaimsTransformation
{
    private readonly ICustomProvider _customProvider;

    public EnhanceClaimsTransformation(ICustomProvider customProvider)
    {
        _customProvider = customProvider;
    }

    /// <summary>
    /// Upon authentication, we transform the claims in order to enhance
    /// the claims with user-enhanced values.
    /// </summary>
    /// <param name="principal"></param>
    /// <returns></returns>
    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // https://gunnarpeipman.com/aspnet-core-adding-claims-to-existing-identity/
        ClaimsPrincipal clone = principal.Clone();
        ClaimsIdentity claimsIdentity = (ClaimsIdentity)clone.Identity;

        Response response = await _customProvider.Find(principal.Identity.Name, CancellationToken.None);

        // Setting claims values
        claimsIdentity.AddClaims(new List<Claim>
        {
            new Claim("Datapoint1", response.Datapoint1),
            new Claim("Datapoint2", response.Datapoint2),
            // ...
        });        

        return clone;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接