
很多初学者都会遇到这种情况:
你看到这样的代码:
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => { ... });
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
{
policy.RequireRole("admin");
});
});
app.MapGet("/admin", (ClaimsPrincipal user) => ...)
.RequireAuthorization("AdminOnly");代码能跑,但心里其实一堆问号:
"Bearer" 是什么?Claim 和 Role 是什么关系?Policy 为什么还能写规则?ClaimsPrincipal 和 ClaimsIdentity 又是什么?HttpContext.User 里的?RequireRole("admin"),有时候写 RequireClaim("age", "18")?很多人学到这里,开始出现一个很大的问题:
代码记住了,但概念没建立起来。
一旦换个场景,比如:
马上又乱了。
所以这篇文章不只是教你“写法”,而是帮你建立一套完整的认知模型。
你看完后,至少要真正明白:
如果你现在只想先有一个整体印象,可以先看这一句话:
认证是“你是谁”,授权是“你能干什么”。
ASP.NET Core 里,大致可以分成下面这几层:
解决的是:
框架怎么从请求里识别用户?
比如:
这就是 Scheme(认证方案)
认证成功以后,框架会构造一个“当前用户对象”,放到:
HttpContext.User这个对象就是:
ClaimsPrincipalClaimsIdentity用户身份里包含很多“声明式信息”,比如:
这些一条条的信息,就是:
Claim(声明)
当接口要求:
这就是:
授权(Authorization)
当授权逻辑比较复杂时,你不想每个接口都重复写规则,于是可以定义:
Policy(策略)
例如:
这是整套体系里最基础的东西。
Claim 可以翻译成:
你可以把它理解成:
关于当前用户的一条“已知事实”
例如:
alice1001adminITalice@test.com这些都可以表示成 Claim。
new Claim(ClaimTypes.Name, "alice")
new Claim(ClaimTypes.NameIdentifier, "1001")
new Claim(ClaimTypes.Role, "admin")
new Claim("department", "IT")每个 Claim 其实就是一个 键值对:
例如:
new Claim("department", "IT")表示:
departmentIT例如某个用户有这些标签:
那么你后续做权限判断时,就可以说:
Role=admin 才能访问管理员接口department=IT 才能访问 IT 系统employee=true 才能访问员工中心 using System.Security.Claims;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "alice"),
new Claim(ClaimTypes.NameIdentifier, "1001"),
new Claim(ClaimTypes.Role, "admin"),
new Claim("department", "IT")
};这就是在描述一个用户的基础资料。
当你有了一堆 Claim 后,它们不能直接单独使用。 框架需要把这些 Claim 组合成一个“身份”。
这个身份对象就是:
ClaimsIdentity
你可以把 ClaimsIdentity 理解成:
一组 Claim 组成的一份“身份信息”
例如:
var identity = new ClaimsIdentity(claims, "Bearer");它表示:
"Bearer" 这个认证方案得到的 using System.Security.Claims;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "alice"),
new Claim(ClaimTypes.NameIdentifier, "1001"),
new Claim(ClaimTypes.Role, "admin")
};
var identity = new ClaimsIdentity(claims, "CustomAuth");此时 identity 里就有:
CustomAuth因为现实世界里,一个用户可能不止一种身份来源。
比如:
所以框架设计成:
这比“只有一个简单用户对象”更灵活。
如果 ClaimsIdentity 是“一份身份”, 那 ClaimsPrincipal 就是:
当前用户整体
它是 ASP.NET Core 里最重要的用户对象。
框架最终会把它放到:
HttpContext.User可以简单理解为:
Claim:一条用户信息ClaimsIdentity:一组用户信息,构成一个身份ClaimsPrincipal:当前用户,里面可以包含一个或多个身份 using System.Security.Claims;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "alice"),
new Claim(ClaimTypes.Role, "admin")
};
var identity = new ClaimsIdentity(claims, "CustomAuth");
var principal = new ClaimsPrincipal(identity);此时:
principal.Identity.Name 是 aliceprincipal.IsInRole("admin") 是 true例如:
app.MapGet("/me", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Name = user.Identity?.Name,
IsAuthenticated = user.Identity?.IsAuthenticated
});
}).RequireAuthorization();这里的 user,本质上就是 HttpContext.User。
这是很多人容易混淆的点。
Role 就是:
角色
比如:
是的。 在 ASP.NET Core 中,Role 本质上也是 Claim。
例如:
new Claim(ClaimTypes.Role, "admin")这就是一个角色 Claim。
因为“角色判断”太常见了,所以框架专门给它做了快捷支持。
例如:
user.IsInRole("admin")或者:
policy.RequireRole("admin")底层本质上,还是在看用户有没有:
ClaimTypes.Role = "admin" using System.Security.Claims;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "alice"),
new Claim(ClaimTypes.Role, "admin")
};
var identity = new ClaimsIdentity(claims, "Demo");
var user = new ClaimsPrincipal(identity);
Console.WriteLine(user.Identity?.Name); // alice
Console.WriteLine(user.IsInRole("admin")); // true现在回到你最开始困惑的 Scheme。
Scheme 的中文一般翻译为:
它解决的问题是:
框架应该用哪种方式来识别当前用户?
"Cookies":从 Cookie 中识别用户"Bearer":从 JWT Bearer Token 中识别用户"Simple":你自己写的自定义方案"ApiKey":你自己写的 API Key 认证方案因为一个系统可能有多种认证方式。
比如:
如果没有 Scheme,框架就不知道:
当前该用哪一套逻辑去处理请求
builder.Services
.AddAuthentication()
.AddCookie("Cookies", options => { })
.AddJwtBearer("Bearer", options => { });这里你注册了两个方案:
CookiesBearer之后授权时就可以指定某个接口该走哪个方案。
现在把这些概念串起来。
认证做的事情是:
从请求里提取凭证,验证凭证有效性,构造当前用户对象
流程大致是:
ClaimsPrincipalHttpContext.User不是“返回 true/false”这么简单。 真正重要的结果是:
生成一个带 Claim 的用户对象
也就是:
ClaimsPrincipal如果凭证无效,或者没有凭证:
NoResultFail如果说认证是在回答:
你是谁?
那么授权回答的是:
你能不能访问这个资源?
比如一个接口:
app.MapGet("/admin", () => "管理员接口")
.RequireAuthorization();这里至少要求:
如果进一步写:
.RequireAuthorization(policy => policy.RequireRole("admin"))那要求就变成:
admin 角色识别身份
根据身份和规则判断是否允许访问
Authentication:先认出你是谁
Authorization:再判断你能做什么这一步就是把授权再往上提一层。
如果你每个接口都手写规则:
.RequireAuthorization(policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("admin");
policy.RequireClaim("department", "IT");
});写多了会很乱,也不好统一维护。
所以框架允许你把规则起个名字,集中定义:
这就是 Policy(策略)
Policy 本质上就是:
一组授权要求的组合
例如:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("admin");
});
});然后接口直接用:
app.MapGet("/admin", () => "管理员接口")
.RequireAuthorization("AdminOnly");这部分非常关键。 你只有理解了请求执行链路,前面的概念才算真正串起来。
假设你访问:
GET /admin
Authorization: Bearer xxx系统里做了这些事:
app.UseAuthentication();开始工作。
例如 Bearer 方案:
认证通过后,框架会创建:
然后放到:
HttpContext.User app.UseAuthorization();发现 /admin 这个接口要求授权。
比如规则是:
框架就会检查:
user.Identity.IsAuthenticatedClaimTypes.Role = admin这段代码不依赖 Web 项目,纯 C# 控制台也能看懂。
using System.Security.Claims;
// 1. 创建用户声明
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "alice"),
new Claim(ClaimTypes.NameIdentifier, "1001"),
new Claim(ClaimTypes.Role, "admin"),
new Claim("department", "IT")
};
// 2. 创建身份
var identity = new ClaimsIdentity(claims, "DemoAuth");
// 3. 创建当前用户
var principal = new ClaimsPrincipal(identity);
// 4. 读取信息
Console.WriteLine($"是否已认证: {principal.Identity?.IsAuthenticated}");
Console.WriteLine($"用户名: {principal.Identity?.Name}");
Console.WriteLine($"用户ID: {principal.FindFirst(ClaimTypes.NameIdentifier)?.Value}");
Console.WriteLine($"部门: {principal.FindFirst("department")?.Value}");
Console.WriteLine($"是否管理员: {principal.IsInRole("admin")}");输出结果类似:
是否已认证: True
用户名: alice
用户ID: 1001
部门: IT
是否管理员: TrueRequireAuthorization() app.MapGet("/me", () => "需要登录")
.RequireAuthorization();含义:
这个接口要求用户已认证
RequireRole("admin") .RequireAuthorization(policy => policy.RequireRole("admin"));含义:
用户必须有
admin角色
RequireClaim("department", "IT") .RequireAuthorization(policy => policy.RequireClaim("department", "IT"));含义:
用户必须带
department=IT这个 Claim
ClaimsPrincipal user app.MapGet("/me", (ClaimsPrincipal user) => ...)含义:
直接注入当前登录用户对象
Role 本质上是 Claim 的一种特殊约定。 所以:
ClaimsIdentity 不是“整个用户”整个用户通常是:
ClaimsPrincipalClaimsIdentity 只是其中一个身份来源。
Scheme 解决的是:
怎么识别用户
Policy / Role / Claim 规则解决的是:
用户识别出来以后,能不能访问
一个用户就算通过认证了,也可能没有访问某接口的权限。 这就是为什么会有:
如果你是初学者,先把这四个吃透:
这四个掌握后,其他概念就容易了。
刚开始只需要:
先把最常见场景跑通,再慢慢扩展。
例如:
不要把这些逻辑散落在所有接口上,应该统一定义成 Policy。
适合:
适合:
换句话说:
最后我帮你用一套最简单的方式收束一下。
Claim -> ClaimsIdentity -> ClaimsPrincipal含义是:
Scheme 负责认证
Policy / Role / Claim 负责授权 请求进来
-> Scheme 识别用户
-> 构造 ClaimsPrincipal
-> 放入 HttpContext.User
-> Authorization 按 Policy / Role / Claim 检查
-> 允许或拒绝访问原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。