Question
如何使两个方案的令牌与角色一起工作,而不管使用的是"默认"方案令牌还是"辅助"方案令牌?
目前,任一令牌都将对具有.RequireAuthorization()
个的API起作用(200响应),但只有属于DefaultAuthenticateScheme
标识的方案的令牌才能对具有.RequireAuthorization(a => a.RequireRole(roleName));
的API起作用.
更改哪个方案DefaultAuthenticateScheme
指向哪些令牌使用需要管理员角色的API的更改,即使两个令牌都具有此角色,尽管它们属于不同的方案.
那么,这里的解决方案是什么?
Reproduction case
代码
这应该是重现问题所需的一切.JWT令牌是使用测试服务生成的(不用担心!这里没有凭据泄露!).
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace AuthorizeProblemSample
{
public class Program
{
public static void Main(string[] args)
{
const string roleName = "Administrator";
const string defaultScheme = "Default";
const string secondaryScheme = "Secondary";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = defaultScheme;
o.DefaultScheme = defaultScheme;
})
// JWT credentials generated for this sample using Jamie Kurtz's JWT Builder.
// No credentials have been harmed in the making of this sample.
// Default scheme token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2OTUyODkzMjMsImV4cCI6MTcyNjgyNTMyMywiYXVkIjoiZGVmYXVsdEF1ZGllbmNlIiwic3ViIjoidXNlcjFAZXhhbXBsZS5jb20ifQ.SH3mxkdJCjdQ4HUX7sRPLJ2_7baW2OwNhB39fnGduD8
.AddJwtBearer(defaultScheme, o => o.Audience = "defaultAudience")
// Secondary scheme token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2OTUyODkzMjMsImV4cCI6MTcyNjgyNTMyMywiYXVkIjoic2Vjb25kYXJ5QXVkaWVuY2UiLCJzdWIiOiJ1c2VyMUBleGFtcGxlLmNvbSJ9.TNamLBog9qxLiebI7F8hu0dX09MjZlGoydKYeDve0ig
.AddJwtBearer(secondaryScheme, o => o.Audience = "secondaryAudience");
builder.Services.ConfigureAll<JwtBearerOptions>(o =>
{
o.TokenValidationParameters = new TokenValidationParameters()
{
ValidateActor = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = false,
ValidateLifetime = false,
ValidateAudience = true,
ValidateTokenReplay = false,
SignatureValidator = (t, v) => new JwtSecurityToken(t)
};
o.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
var claims = context.Principal!.Claims.Append(new Claim(ClaimTypes.Role, roleName));
var claimsIdentity = new ClaimsIdentity(claims, context.Principal!.Identity!.AuthenticationType, ClaimTypes.Name, ClaimTypes.Role);
context.Principal = new ClaimsPrincipal(claimsIdentity);
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization(opts =>
{
const string policyName = "myPolicy";
opts.AddPolicy(policyName, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes(defaultScheme, secondaryScheme);
});
opts.DefaultPolicy = opts.GetPolicy(policyName)!;
});
var app = builder.Build();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.MapGet("/一", () => "hello").RequireAuthorization();
app.MapGet("/两个", () => "world").RequireAuthorization(a => a.RequireRole(roleName));
app.MapGet("/INFO", (HttpRequest req) =>
{
var result = new StringBuilder();
result.AppendFormat("User is in {0} role?: {1}", roleName, req.HttpContext.User.IsInRole(roleName));
result.AppendLine();
result.AppendFormat("User is authenticated?: {0}", req.HttpContext.User.Identity.IsAuthenticated);
result.AppendLine();
var roleClaims = req.HttpContext.User.Claims.Where(c => c.Type == ClaimTypes.Role);
foreach (var roleClaim in roleClaims)
{
result.AppendFormat("Role: {0}", roleClaim.Value);
}
return result.ToString();
}).RequireAuthorization();
app.Run();
}
}
}
正如您所看到的,在通过OnTokenValidated
事件向入站请求添加Administrator
角色时,这两种方案的工作方式是相同的.
创建演示项目的步骤
- 创建新的空ASP.NET 7.0 Web API(使用 top-level ,不需要HTTPS或Docker).
- 添加以下程序包:
<ItemGroup>
<PackageReference Include="IdentityModel" Version="6.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
</ItemGroup>
- 将上面的整个代码粘贴到Program.cs中
- 运行它并发出请求
端点
/INFO
如果您使用任一JWT令牌请求/INFO
,您将看到该令牌所代表的用户确实具有管理员角色,它被认为是经过身份验证的,并且这是唯一的角色.
输出如下所示:
User is in Administrator role?: True
User is authenticated?: True
Role: Administrator
/一
当针对/一
个令牌发出请求时,这两个令牌都可以工作(200响应),大概是因为它不需要判断角色.当你调用它的时候,你应该会得到"Hello"的结果.
/两个
/两个
要求调用者具有角色Administrator
.您将观察到的行为是,它适用于属于DefaultScheme
方案(200响应)的令牌,但不适用于属于SecondaryScheme
方案(403响应)的令牌.
但如果您随后更改DefaultAuthenticateScheme = SecondaryScheme
,则相反的情况将变为正确:/两个
将适用于属于SecondaryScheme
(200响应)的令牌,但不适用于属于DefaultScheme
(403响应)的令牌.这就是问题所在.
Notes
- 同样的问题也发生在用
[Authorize]
或[Authorize(Roles = "Administrator")]
属性修饰的非最小API上,但我在这里使用了最小API来减少示例代码的大小. - 我想知道我在
OnTokenValidated
中的代码是否实际上没有按照我预期的方式工作,但是注释掉整个事件会导致用户在调用/INFO
时当然没有Administrator
或任何角色,并且在调用/两个
时拒绝这两个令牌,所以很明显,当调用/两个
时,它是代码的一部分,使得DefaultAuthenticateScheme
‘S令牌成功. - 我不确定这些是Windows权限还是任何类型的操作系统权限的 idea 是从哪里来的.用户是应用程序级别的用户,权限是应用程序级别的权限,旨在控制对API中某些功能的访问.尽管我的项目没有使用ASP.NET核心标识,但角色是基于对经过身份验证的用户的角色声明,并且同样,角色是应用程序域的一部分(请参见this question),关于如何为使用ASP.NET核心标识的用户创建角色以更好地了解它们是什么.