用户在Blazor Webassembly认证和授权中具有多角色时的问题?



我有一个Blazor Webassembly解决方案,当我登录用户(有多个角色),登录操作显示错误:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.ArgumentException: An item with the same key has already been added. Key: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
at REMO.Server.Controllers.AuthController.CurrentUserInfo() in D:REMOREMOServerControllersAuthController.cs:line 87
at lambda_method29(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean&
isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

跟随我的自定义身份验证提供程序:

public class CustomStateProvider : AuthenticationStateProvider
{
private readonly IAuthService api;
private CurrentUser _currentUser;
public CustomStateProvider(IAuthService api)
{
this.api = api;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity();
try
{
var userInfo = await GetCurrentUser();
var roleClaims = identity.FindAll(identity.RoleClaimType);
if (userInfo.IsAuthenticated)

{
var claims = new[] 
{ 
new Claim(ClaimTypes.Name, _currentUser.UserName)
}
.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));
identity = new ClaimsIdentity(claims, "Server authentication");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine("Request failed:" + ex.ToString());
}
return new AuthenticationState(new ClaimsPrincipal(identity));
}

Startup.cs

...
services.AddDbContext<ApplicationDBContext>(options => options .UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDBContext>();
...

问题似乎是一个身份存在多个角色。
如果一个身份只有一个角色,就不会出现这个问题。

您的问题是角色的构造索赔的关键。
如果有多个角色,这个键必须是数组
不能有多个"角色"。输入JWT令牌

我认为你已经使用了Mukesh的代码,这是一个很好的起点,但是如果你阅读评论,最后一个解释了像你这样的问题。

所以你需要修改 一行
.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));

通过LINQ提取所有声明不是将类型为role的角色添加到claims数组中。
现在您需要创建一个数组,其中包含类型为角色的所有声明(我认为它们来自您的API),并添加一个数组类型的角色声明条目。

经过这样的修改,我想它应该可以工作了。

结果解码的JWT令牌应该具有如下形式:
{
"sub": "nbiada",
"jti": "123...",
"role" : [
"User",
"Admin"
],
"exp": 12345467,
...
}

注意:我缩短了role键,在你的实现中应该是http://schemas.microsoft.com/ws/2008/06/identity/claims/role

正如Nicola Biada所说,您的问题是角色声明键的构造。我实现Mukesh Sample,并在AuthController中修改CurrentUserInfo动作,以将角色声明作为数组发送。这是我的动作

public CurrentUser CurrentUserInfo()
{
var roles = User.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray();
var json = JsonSerializer.Serialize(roles);
var claims = User.Claims.Where(c => c.Type != ClaimTypes.Role).ToDictionary(c => c.Type, c => c.Value);
claims.Add(ClaimTypes.Role, json);
return new CurrentUser
{
IsAuthenticated = User.Identity.IsAuthenticated,
UserName = User.Identity.Name,
Claims = claims
};
}

然后在客户端返回到先前的状态。为此,我在CustomStateProvider中更改GetAuthenticationStateAsync。

public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity();
try
{
var userInfo = await GetCurrentUser();
if (userInfo.IsAuthenticated)
{
var claims = new[] { new Claim(ClaimTypes.Name, _currentUser.UserName) }.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value))).ToList();
var roles = claims.FirstOrDefault(c => c.Type == ClaimTypes.Role);
claims.Remove(roles);
var rolesString = JsonSerializer.Deserialize<string[]>(roles.Value);
foreach (var role in rolesString)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
identity = new ClaimsIdentity(claims, "Server authentication");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine("Request failed:" + ex.ToString());
}
return new AuthenticationState(new ClaimsPrincipal(identity));
}

最新更新