转换 ASP.Net Core 中的 Open Id Connect 声明
Transforming Open Id Connect claims in ASP.Net Core
我正在编写 ASP.Net 核心 Web 应用程序并使用 UseOpenIdConnectAuthentication
将其连接到 IdentityServer3。模拟他们的 ASP.Net MVC 5 示例,我正在尝试转换从 Identity Server 收到的声明以删除“low level protocol claims that are certainly not needed”。在 MVC 5 中,他们为 SecurityTokenValidated Notification 添加了一个处理程序,将 AuthenticationTicket
换成仅具有所需声明的处理程序。
在 ASP.Net 核心中,为了做等效的事情,我认为我需要处理 OpenIdConnectEvents
中的 OnTokenValidated
。但是,在那个阶段似乎没有检索到额外的范围信息。如果我处理 OnUserInformationReceived
,则存在额外信息,但存储在用户而不是主体。
None 其他事件似乎是永久删除我不想在身份验证完成后保留的声明的明显位置。非常感谢收到任何建议!
您可以实现 SignInScheme
的 OnSigningIn
事件。这是一个例子:
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);
感谢 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);
}
}
我不确定这是否是正确的方法,但它似乎有效。
我个人更喜欢在进行实际身份验证的中间件中进行声明转换。
您可以为此使用 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;
}
}
};
我喜欢 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 提早转换声明的建议。
多亏了这个帖子中的答案,我自己也能做到这一点。这里没有解决的是,如果需要修改索赔,这需要一项服务。在我的例子中,我需要构建服务提供者以获得正确的依赖项来转换声明。 (我还能够在不进行转换的情况下删除声明 - 此处也显示了该代码)。
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
(ICustomProvider 在 ConfigureServices
的依赖注入中注册)
请注意,在此代码中,我们需要对主体进行 克隆 以实际向其添加声明。
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;
}
}
我正在编写 ASP.Net 核心 Web 应用程序并使用 UseOpenIdConnectAuthentication
将其连接到 IdentityServer3。模拟他们的 ASP.Net MVC 5 示例,我正在尝试转换从 Identity Server 收到的声明以删除“low level protocol claims that are certainly not needed”。在 MVC 5 中,他们为 SecurityTokenValidated Notification 添加了一个处理程序,将 AuthenticationTicket
换成仅具有所需声明的处理程序。
在 ASP.Net 核心中,为了做等效的事情,我认为我需要处理 OpenIdConnectEvents
中的 OnTokenValidated
。但是,在那个阶段似乎没有检索到额外的范围信息。如果我处理 OnUserInformationReceived
,则存在额外信息,但存储在用户而不是主体。
None 其他事件似乎是永久删除我不想在身份验证完成后保留的声明的明显位置。非常感谢收到任何建议!
您可以实现 SignInScheme
的 OnSigningIn
事件。这是一个例子:
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);
感谢 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);
}
}
我不确定这是否是正确的方法,但它似乎有效。
我个人更喜欢在进行实际身份验证的中间件中进行声明转换。
您可以为此使用 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;
}
}
};
我喜欢 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 提早转换声明的建议。
多亏了这个帖子中的答案,我自己也能做到这一点。这里没有解决的是,如果需要修改索赔,这需要一项服务。在我的例子中,我需要构建服务提供者以获得正确的依赖项来转换声明。 (我还能够在不进行转换的情况下删除声明 - 此处也显示了该代码)。
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
(ICustomProvider 在 ConfigureServices
的依赖注入中注册)
请注意,在此代码中,我们需要对主体进行 克隆 以实际向其添加声明。
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;
}
}