我正在尝试在Blazor Server应用程序中提供自定义角色。通过Windows身份验证的用户应根据其Active Directory组(一个组代表一个角色(获得其中一个自定义角色。
如果用户在正确的组中,则将为该用户提供类型为RoleClaimType的索赔。这些声明稍后用于授权某些页面和操作。
我还没有看到任何人谈论过使用Blazor服务器的Windows身份验证和Active Directory,所以我有这些问题。这是我的尝试,但它是由这里和那里的部分混合而成。所以我不确定这是最好的方法还是不安全。
这就是我到目前为止的想法。。
ClaimTransformer.cs,我从appsettings.json.得到了Adgroup
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
string adGroup = _configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("AdGroup").Value;
if (principal.IsInRole(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();
...
}
我还在Startup.cs中注册了ClaimsTransformer,注册地址为:services.AddScoped<IClaimsTransformation, ClaimsTransformer>();
授权整个Blazor组件:
@attribute [Authorize(Roles = "CustomRole")]
或授权组件的部件:
<AuthorizeView Roles="CustomRole">
<Authorized>You are authorized</Authorized>
</AuthorizeView>
所以我的问题基本上是:
-这些索赔必须重新申请吗?如果它们过期了,什么时候到期过期
-这种授权的最佳实践是什么
-这样安全吗
你的问题有点老了,我想你已经找到了一个解决方案,不管怎样,也许还有其他人希望在Windows身份验证中实现自定义角色,所以我找到的最简单的方法是:
在服务或竞争对手中,您可以注入AuthenticationStateProvider
,然后注入
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
var userClaims = new ClaimsIdentity(new List<Claim>()
{
new Claim(ClaimTypes.Role,"Admin")
});
user.AddIdentity(userClaims);
通过这种方式,您可以设置新的角色。
当然,您可以实现自定义逻辑来为每个用户动态添加角色。
这就是我最终添加基于AD组的角色的方式:
public async void GetUserAD()
{
var auth = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = (System.Security.Principal.WindowsPrincipal)auth.User;
using PrincipalContext pc = new PrincipalContext(ContextType.Domain);
UserPrincipal up = UserPrincipal.FindByIdentity(pc, user.Identity.Name);
FirstName = up.GivenName;
LastName = up.Surname;
UserEmail = up.EmailAddress;
LastLogon = up.LastLogon;
FixPhone = up.VoiceTelephoneNumber;
UserDisplayName = up.DisplayName;
JobTitle = up.Description;
DirectoryEntry directoryEntry = up.GetUnderlyingObject() as DirectoryEntry;
Department = directoryEntry.Properties["department"]?.Value as string;
MobilePhone = directoryEntry.Properties["mobile"]?.Value as string;
MemberOf = directoryEntry.Properties["memberof"]?.OfType<string>()?.ToList();
if(MemberOf.Any(x=>x.Contains("management-team") && x.Contains("OU=Distribution-Groups")))
{
var userClaims = new ClaimsIdentity(new List<Claim>()
{
new Claim(ClaimTypes.Role,"Big-Boss")
});
user.AddIdentity(userClaims);
}
}
编辑
下面你可以找到我如何加载用户信息和分配角色的示例
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
public class UserService : IUserService
{
private readonly AuthenticationStateProvider authenticationStateProvider;
private readonly ApplicationDbContext context;
public ApplicationUser CurrentUser { get; private set; }
public UserService(AuthenticationStateProvider authenticationStateProvider, ApplicationDbContext context)
{
this.authenticationStateProvider = authenticationStateProvider;
this.context = context;
}
public async Task LoadCurrentUserInfoAsync()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
using PrincipalContext principalContext = new PrincipalContext(ContextType.Domain);
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, authState.User.Identity.Name);
DirectoryEntry directoryEntry = userPrincipal.GetUnderlyingObject() as DirectoryEntry;
CurrentUser.UserName = userPrincipal.SamAccountName;
CurrentUser.FirstName = userPrincipal.GivenName;
CurrentUser.LastName = userPrincipal.Surname;
CurrentUser.Email = userPrincipal.EmailAddress;
CurrentUser.FixPhone = userPrincipal.VoiceTelephoneNumber;
CurrentUser.DisplayName = userPrincipal.DisplayName;
CurrentUser.JobTitle = userPrincipal.Description;
CurrentUser.Department = directoryEntry.Properties["department"]?.Value as string;
CurrentUser.MobilePhone = directoryEntry.Properties["mobile"]?.Value as string;
//get user roles from Database
var roles = context.UserRole
.Include(a => a.User)
.Include(a => a.Role)
.Where(a => a.User.UserName == CurrentUser.UserName)
.Select(a => a.Role.Name.ToLower())
.ToList();
var claimsIdentity = authState.User.Identity as ClaimsIdentity;
//add custom roles from DataBase
foreach (var role in roles)
{
var claim = new Claim(claimsIdentity.RoleClaimType, role);
claimsIdentity.AddClaim(claim);
}
//add other types of claims
var claimFullName = new Claim("fullname", CurrentUser.DisplayName);
var claimEmail = new Claim("email", CurrentUser.Email);
claimsIdentity.AddClaim(claimFullName);
claimsIdentity.AddClaim(claimEmail);
}
}
我采用了与您类似的方法,但我在作用域服务中创建了一个专用ClaimsPrincipal对象,以存储在每次TransformAsync调用后发现更改丢失时添加的策略。然后,我添加了一个简单的UserInfo类来获取经过身份验证的用户所属的所有组。
这些索赔必须重新申请吗?如果它们过期,什么时候过期
据我所知,每次调用AuthenticateAsync时都必须重新应用声明。我不确定它们是否过期,但我认为Blazor Server可能会在向客户端发送新的diff之前运行TransformAsync,这样它就永远不会被注意到。
这种授权的最佳做法是什么
不知道,但只要你使用Blazor服务器,内置的身份验证和授权中间件可能是最好的方法之一。WASM将是另一回事。。。
这样安全吗
我认为安全问题最终会更多地集中在Web服务器上,而不是分配角色的方式上。总的来说,它应该是相对安全的,我认为最大的安全问题将取决于等问题
- 当用户从提供访问权限的组中删除时,应用程序应该立即吊销权限,还是可以在下次登录时反映出来
- 将一个用户添加到一个无意中为其提供访问权限的组中有多容易
- 如果权限基于其他用户属性(如OU(,如果目录发生更改,用户是否会错误地获得或失去访问权限
UserAuthorizationService:
public class UserAuthorizationService : IClaimsTransformation {
public UserInfo userInfo;
private ClaimsPrincipal CustomClaimsPrincipal;
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) {
//Creates UserInfo Object on the first Call Only
if (userInfo == null)
userInfo = new UserInfo((principal.Identity as WindowsIdentity).Owner.Value); //Owner.Value Stores SID On Smart Card
//Establishes CustomClaimsPrincipal on first Call
if (CustomClaimsPrincipal == null) {
CustomClaimsPrincipal = principal;
var claimsIdentity = new ClaimsIdentity();
//Loop through AD Group list and applies policies
foreach (var group in userInfo.ADGroups) {
switch (group) {
case "Example AD Group Name":
claimsIdentity.AddClaim(new Claim("ExampleClaim", "Test"));
break;
}
}
CustomClaimsPrincipal.AddIdentity(claimsIdentity);
}
return Task.FromResult(CustomClaimsPrincipal);
}
}
用户信息:
public class UserInfo {
private DirectoryEntry User { get; set; }
public List<string> ADGroups { get; set; }
public UserInfo(string SID) {
ADGroups = new List<string>();
//Retrieve Current User with SID pulled from Smart Card
using (DirectorySearcher comps = new DirectorySearcher(new DirectoryEntry("LDAP String For AD"))) {
comps.Filter = "(&(objectClass=user)(objectSID=" + SID + "))";
User = comps.FindOne().GetDirectoryEntry();
}
//Load List with AD Group Names
foreach (object group in User.Properties["memberOf"])
ADGroups.Add(group.ToString()[3..].Split(",OU=")[0]);
}
}