.NET Core 3.0 声明转换
.NET Core 3.0 ClaimsTransformation
以前在 .NET Framework 中,我使用自定义 RoleProvider
和 Windows 身份验证来针对当前主体提供自定义角色,而不是使用 Active Directory 组。
因此,目标是能够使用装饰性 [Authorize(Roles="")]
属性,其中角色来自数据库而不是活动目录(或者两者的组合都可以)。
为了在核心实现这一点,我相信我需要使用 IClaimsTransformation
来分配角色声明,如 所讨论的那样。
这里我只是想添加一个角色 "Admin" 但是当我使用 [Authorize(Roles = "Admin")]
时我得到了 403 Unauthorized 响应。
Startup.cs
services.AddRazorPages();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
-------
app.UseAuthorization();
ClaimsTransformer.cs
public class ClaimsTransformer : IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
var c = new Claim(identity.RoleClaimType, "Admin");
identity.AddClaim(c);
return await Task.FromResult(principal);
}
}
令人讨厌的是,当我调用 User.IsInRole()
时,这会起作用,并且当我检查声明时可以看到该组,因此正在添加它,但它不适用于 Authorize 属性。如有任何建议,我们将不胜感激。
通过使用 ClaimsTransformer
和自定义 TypeFilterAttribute
解决了这个问题
ClaimsTransformer.cs
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (WindowsIdentity)principal.Identity;
Guid userGuid;
SecurityIdentifier sid = identity.User;
using (DirectoryEntry userDirectoryEntry = new DirectoryEntry("LDAP://<SID=" + sid.Value + ">"))
{
userGuid = userDirectoryEntry.Guid;
}
UserAccount user = null;
if (userGuid != Guid.Empty)
user = await db.UserAccounts.Where(x => x.GUID == userGuid).SingleOrDefaultAsync();
if (user == null)
return principal;
if (user.Historic)
return principal;
var claims = new List<Claim>();
foreach (var role in user?.UserAccountGroups)
{
claims.Add(new Claim(ClaimTypes.GroupSid, role.Group.Name));
};
identity.AddClaims(claims);
return principal;
}
GroupsAttribute.cs
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class GroupsAttribute : TypeFilterAttribute
{
public GroupsAttribute(string groups) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { groups };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly string _groups;
public ClaimRequirementFilter(string groups)
{
_groups = groups;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var groups = _groups.Split(',');
bool hasClaim = false;
foreach (var group in groups)
{
if (context.HttpContext.User.Claims.Any(c => c.Type == ClaimTypes.GroupSid && c.Value.Equals(group.Trim(), StringComparison.OrdinalIgnoreCase)))
hasClaim = true;
}
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
我使用类似的方法根据用户的 Active Directory 组向用户提供自定义角色声明。
public class ClaimsTransformer : IClaimsTransformation
{
private readonly IConfiguration _configuration;
public ClaimsTransformer(IConfiguration configuration)
{
_configuration = configuration;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
// AppRole has two string props, Displayname and AdGroup. Get Adgroup from appsettings.json.
AppRole customRole = new AppRole()
{
DisplayName =_configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("DisplayName").Value,
AdGroup = _configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("AdGroup").Value
};
if (principal.IsInRole(customRole.AdGroup))
{
Claim customRoleClaim = new Claim(claimsIdentity.RoleClaimType, "CustomRole");
claimsIdentity.AddClaim(customRoleClaim);
}
return Task.FromResult(principal);
}
}
要让 Claimstransformer 使用 Authorize 属性,请在 Startup.cs 中使用它:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthorization();
app.UseAuthentication();
...
}
我是用在Blazor上的,所以在Blazor Component中可以这样使用。
授权整个组件:
@attribute [Authorize(Roles = "CustomRole")]
或授权部分组件:
<AuthorizeView Roles="CustomRole">
<Authorized>You are authorized</Authorized>
</AuthorizeView>
我能够通过 Startup.cs
中的策略设置获得授权
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("AdminOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Admin"));
});
以前在 .NET Framework 中,我使用自定义 RoleProvider
和 Windows 身份验证来针对当前主体提供自定义角色,而不是使用 Active Directory 组。
因此,目标是能够使用装饰性 [Authorize(Roles="")]
属性,其中角色来自数据库而不是活动目录(或者两者的组合都可以)。
为了在核心实现这一点,我相信我需要使用 IClaimsTransformation
来分配角色声明,如
这里我只是想添加一个角色 "Admin" 但是当我使用 [Authorize(Roles = "Admin")]
时我得到了 403 Unauthorized 响应。
Startup.cs
services.AddRazorPages();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
-------
app.UseAuthorization();
ClaimsTransformer.cs
public class ClaimsTransformer : IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
var c = new Claim(identity.RoleClaimType, "Admin");
identity.AddClaim(c);
return await Task.FromResult(principal);
}
}
令人讨厌的是,当我调用 User.IsInRole()
时,这会起作用,并且当我检查声明时可以看到该组,因此正在添加它,但它不适用于 Authorize 属性。如有任何建议,我们将不胜感激。
通过使用 ClaimsTransformer
和自定义 TypeFilterAttribute
ClaimsTransformer.cs
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (WindowsIdentity)principal.Identity;
Guid userGuid;
SecurityIdentifier sid = identity.User;
using (DirectoryEntry userDirectoryEntry = new DirectoryEntry("LDAP://<SID=" + sid.Value + ">"))
{
userGuid = userDirectoryEntry.Guid;
}
UserAccount user = null;
if (userGuid != Guid.Empty)
user = await db.UserAccounts.Where(x => x.GUID == userGuid).SingleOrDefaultAsync();
if (user == null)
return principal;
if (user.Historic)
return principal;
var claims = new List<Claim>();
foreach (var role in user?.UserAccountGroups)
{
claims.Add(new Claim(ClaimTypes.GroupSid, role.Group.Name));
};
identity.AddClaims(claims);
return principal;
}
GroupsAttribute.cs
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class GroupsAttribute : TypeFilterAttribute
{
public GroupsAttribute(string groups) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { groups };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly string _groups;
public ClaimRequirementFilter(string groups)
{
_groups = groups;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var groups = _groups.Split(',');
bool hasClaim = false;
foreach (var group in groups)
{
if (context.HttpContext.User.Claims.Any(c => c.Type == ClaimTypes.GroupSid && c.Value.Equals(group.Trim(), StringComparison.OrdinalIgnoreCase)))
hasClaim = true;
}
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
我使用类似的方法根据用户的 Active Directory 组向用户提供自定义角色声明。
public class ClaimsTransformer : IClaimsTransformation
{
private readonly IConfiguration _configuration;
public ClaimsTransformer(IConfiguration configuration)
{
_configuration = configuration;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
// AppRole has two string props, Displayname and AdGroup. Get Adgroup from appsettings.json.
AppRole customRole = new AppRole()
{
DisplayName =_configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("DisplayName").Value,
AdGroup = _configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("AdGroup").Value
};
if (principal.IsInRole(customRole.AdGroup))
{
Claim customRoleClaim = new Claim(claimsIdentity.RoleClaimType, "CustomRole");
claimsIdentity.AddClaim(customRoleClaim);
}
return Task.FromResult(principal);
}
}
要让 Claimstransformer 使用 Authorize 属性,请在 Startup.cs 中使用它:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthorization();
app.UseAuthentication();
...
}
我是用在Blazor上的,所以在Blazor Component中可以这样使用。
授权整个组件:
@attribute [Authorize(Roles = "CustomRole")]
或授权部分组件:
<AuthorizeView Roles="CustomRole">
<Authorized>You are authorized</Authorized>
</AuthorizeView>
我能够通过 Startup.cs
中的策略设置获得授权services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("AdminOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Admin"));
});