用户在 Blazor Webassembly 身份验证和授权中具有多个角色时出现问题?
Problem when user has multi roles in Blazor Webassembly authentication and authorisation?
我有一个 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:\REMO\REMO\Server\Controllers\AuthController.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>();
...
问题似乎是一个身份存在多个角色。
如果身份只有一个角色,则不会出现此问题。
您的问题是角色声明密钥的构造。
在多个角色的情况下,此密钥必须是 array.
您的 JWT 令牌中不能有多个“角色”密钥。
我认为您已经使用了 Mukesh 中的代码并且是一个很好的起点,但是如果您阅读评论,最后一个解释了与您一样的问题。
所以你需要修改行
.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));
通过 LINQ 提取所有 不是 类型 role 的声明,并将它们添加到 claims
数组。
现在您需要创建一个包含所有类型为 role 的声明的数组(我认为它们来自您的 API)并添加一个 role 声明数组类型的条目。
修改后我觉得应该可以了。
生成的解码 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
};
}
然后在客户端i return回到之前的状态。为此,我更改了 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));
}
我有一个 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:\REMO\REMO\Server\Controllers\AuthController.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>();
...
问题似乎是一个身份存在多个角色。
如果身份只有一个角色,则不会出现此问题。
您的问题是角色声明密钥的构造。
在多个角色的情况下,此密钥必须是 array.
您的 JWT 令牌中不能有多个“角色”密钥。
我认为您已经使用了 Mukesh 中的代码并且是一个很好的起点,但是如果您阅读评论,最后一个解释了与您一样的问题。
所以你需要修改行
.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));
通过 LINQ 提取所有 不是 类型 role 的声明,并将它们添加到 claims
数组。
现在您需要创建一个包含所有类型为 role 的声明的数组(我认为它们来自您的 API)并添加一个 role 声明数组类型的条目。
修改后我觉得应该可以了。
生成的解码 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
};
}
然后在客户端i return回到之前的状态。为此,我更改了 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));
}