.NET 认证与权限基础
做后台系统或接口服务时,认证与权限几乎是绕不过去的主题。很多初学者能很快写出一个登录接口,但一到真正的权限控制场景就开始混乱:
- 登录成功后怎么识别当前用户?
- 接口到底应该返回
401还是403? Role和Policy有什么区别?- 为什么
[Authorize]加上了却不生效?
这些问题的根源,往往不是 API 不会写,而是没有把认证与授权的整体模型理解清楚。
第一章 先区分认证和授权
这是必须先分清楚的两个概念。
1.1 认证:你是谁
认证的目标是确认当前请求的身份。
例如:
- 用户输入用户名密码登录
- 系统校验通过
- 系统确认“这是用户 Tom”
这就是认证。
1.2 授权:你能做什么
授权的目标是判断当前身份是否允许执行某个操作。
例如:
- Tom 登录成功了
- 但他没有管理员权限
- 所以不能访问删除用户接口
这就是授权。
1.3 最常见的误区
很多人把“登录成功”直接等同于“有权限访问所有接口”,这是不对的。
正确顺序通常是:
- 先认证
- 再授权
第二章 ASP.NET Core 中和安全相关的核心概念
在 ASP.NET Core 里,认证授权通常围绕下面这些关键词展开:
AuthenticationAuthorizationClaimsIdentityRolesPolicies
可以先建立一个总模型:
登录
-> 服务端签发令牌 / 建立身份信息
-> 请求进入系统
-> Authentication 识别当前用户
-> Authorization 判断是否允许访问
-> 控制器 / 端点继续执行
第三章 JWT 为什么这么常见
在前后端分离项目里,最常见的接口鉴权方案就是 JWT。
3.1 什么是 JWT
JWT 全称是 JSON Web Token。你可以把它理解成一个经过签名的身份票据,里面通常会携带:
- 用户 ID
- 用户名
- 角色
- 权限点
- 过期时间
3.2 一个典型流程
- 用户提交用户名密码
- 服务端校验账号密码
- 校验通过后签发 JWT
- 客户端保存 Token
- 之后每次请求都带上
Authorization: Bearer <token> - 服务端校验 Token 并恢复当前用户身份
请求头通常是这样:
Authorization: Bearer your-jwt-token
3.3 JWT 适合什么场景
它特别适合:
- 前后端分离项目
- 移动端接口
- 多服务之间的轻量身份传递
但要注意,JWT 不是“有了就天下无敌”,它仍然需要:
- 正确签名
- 合理过期时间
- 配合刷新机制或重新登录策略
第四章 如何在 ASP.NET Core 中配置 JWT 认证
4.1 配置项示例
appsettings.json:
{
"Jwt": {
"Issuer": "demo-api",
"Audience": "demo-client",
"Key": "your-secret-key-for-demo",
"ExpiresMinutes": 120
}
}
4.2 注册认证服务
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
});
builder.Services.AddAuthorization();
4.3 中间件顺序
app.UseAuthentication();
app.UseAuthorization();
顺序不能反。
原因很简单:
UseAuthentication()负责先识别当前用户UseAuthorization()负责根据身份做权限判断
如果连身份都还没建立,授权当然无从谈起。
第五章 登录接口通常会做什么
很多人只会抄一段签发 Token 的代码,但不清楚登录接口到底承担什么职责。
5.1 登录接口的基本职责
通常包括:
- 接收账号密码
- 校验用户是否存在
- 校验密码是否正确
- 组装 Claims
- 生成 Token
- 返回给客户端
5.2 一个简化示例
[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
if (request.UserName != "admin" || request.Password != "123456")
{
return Unauthorized("用户名或密码错误");
}
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, "1"),
new(ClaimTypes.Name, "admin"),
new(ClaimTypes.Role, "Admin"),
new("permission", "users.manage")
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(2),
signingCredentials: credentials);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return Ok(new { token = tokenString });
}
public class LoginRequest
{
public string UserName { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
这个例子只是教学版,真实项目里还要考虑:
- 密码加密存储
- 登录失败次数限制
- 刷新令牌
- 审计日志
第六章 [Authorize] 怎么保护接口
如果一个接口必须在登录后才能访问,最基础的做法就是加 [Authorize]。
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ProfileController : ControllerBase
{
[HttpGet]
public IActionResult GetProfile()
{
return Ok("当前用户资料");
}
}
6.1 [AllowAnonymous]
如果某个接口允许匿名访问,比如登录接口、验证码接口,就可以显式标记:
[AllowAnonymous]
[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
return Ok();
}
6.2 常见理解
[Authorize]:要求先通过认证[AllowAnonymous]:允许匿名访问
第七章 Claims 到底是什么
Claims 是整个认证授权体系里非常关键的概念。
可以把它理解成:当前登录用户随身携带的一组身份信息。
7.1 常见 Claim 内容
- 用户 ID
- 用户名
- 角色
- 权限点
- 租户信息
- 部门信息
7.2 在业务代码里读取
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var userName = User.Identity?.Name;
var role = User.FindFirst(ClaimTypes.Role)?.Value;
7.3 为什么 Claims 很重要
因为后面的权限判断很多都建立在 Claims 基础上。
例如:
- 你是不是管理员
- 你有没有某个权限点
- 你属于哪个租户
这些信息都可以放在 Claims 里。
第八章 基于角色的授权
角色授权是最容易理解的一种方式。
8.1 示例
[Authorize(Roles = "Admin")]
[HttpGet("admin")]
public IActionResult GetAdminData()
{
return Ok("管理员数据");
}
这表示当前用户必须带有 Admin 角色才能访问。
8.2 角色授权适合什么场景
适合:
- 简单后台系统
- 角色层级不复杂的项目
不太适合:
- 权限粒度非常细的系统
- 动态权限点控制很多的项目
因为角色通常比较粗粒度。
第九章 基于策略的授权
策略授权比角色授权更灵活,也更适合中大型系统。
9.1 注册策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ManageUsers", policy =>
policy.RequireClaim("permission", "users.manage"));
});
9.2 使用策略
[Authorize(Policy = "ManageUsers")]
[HttpGet("manage-users")]
public IActionResult ManageUsers()
{
return Ok("用户管理数据");
}
9.3 为什么策略更适合复杂系统
因为你可以把权限表达得更细:
- 某个权限点
- 某类资源访问条件
- 多条件组合判断
相比只靠 Role,它的表达能力更强。
第十章 401 和 403 到底怎么区分
这是权限排查时最常见的问题之一。
10.1 401 Unauthorized
通常表示:
- 没带 Token
- Token 无效
- Token 过期
- 签名校验失败
也就是说,问题通常出在“认证没通过”。
10.2 403 Forbidden
通常表示:
- 已经认证成功
- 但当前用户没有访问该资源的权限
也就是说,问题通常出在“授权没通过”。
10.3 一个简单记忆方式
401:你是谁都没确认成功403:你是谁确认了,但你不能做这件事
第十一章 一个后台系统的常见权限设计思路
对于中后台系统,一个比较常见的做法是:
- 登录成功后签发 JWT
- 把用户标识、角色、权限点写入 Claims
- 接口层通过
[Authorize]+ Policy 控制访问 - 前端根据权限点决定菜单和按钮是否展示
11.1 为什么前后端都要参与权限控制
因为它们的职责不同:
- 后端:真正决定是否允许访问
- 前端:优化展示体验,隐藏无权限菜单和按钮
不能只靠前端控制,也不能完全忽视前端的展示控制。
第十二章 常见坑与排查思路
12.1 只注册了 AddAuthentication,忘了中间件
这是最常见问题之一。
只配置服务,不加:
app.UseAuthentication();
请求仍然不会自动建立身份。
12.2 中间件顺序错误
比如把:
app.UseAuthorization();
app.UseAuthentication();
写反了,授权自然会出问题。
12.3 Token 配置不一致
常见包括:
- Issuer 不一致
- Audience 不一致
- 签名密钥不一致
12.4 Claims 没写进去
接口上写了:
[Authorize(Roles = "Admin")]
但你签发 Token 时根本没写 Admin 角色,那授权当然过不去。
12.5 把权限控制都写死在控制器里
小项目还行,大项目会非常难维护。更好的做法是:
- 统一策略命名
- 统一权限点设计
- 结合业务层组织
第十三章 安全实践上的几个建议
13.1 不要明文存密码
密码应该使用安全的哈希方式存储,而不是明文或可逆加密。
13.2 Token 过期时间不要无限长
过长的有效期会增加泄漏风险。
13.3 密钥不要硬编码在代码里
应通过安全配置方式管理,例如环境变量、密钥服务或配置中心。
13.4 认证和授权要分层考虑
不要把“登录成功”和“拥有所有权限”混为一谈。
第十四章 入门阶段建议掌握到什么程度
如果你正在学习 .NET 安全基础,建议至少掌握:
- 能清楚区分认证和授权
- 理解 JWT 的基本流程
- 会配置
AddAuthentication和AddAuthorization - 知道
[Authorize]、[AllowAnonymous]的用法 - 能理解
Claims、Role、Policy的区别 - 能快速区分
401和403
掌握这些后,你就已经具备了做基础后台权限系统的安全基础。
下一步阅读
- 如果你还没把请求处理、中间件和控制器主线串起来,先回看
ASP.NET Core 基础 - 如果你想把权限失败、业务错误和接口错误响应统一起来,阅读
ASP.NET Core 统一异常处理与统一返回 - 如果你想继续补齐发布和部署阶段的安全配置,阅读
.NET 测试、发布与 Docker 部署入门
本文小结
.NET 中的认证与权限控制,并不只是“写一个登录接口,再给接口加个 [Authorize]”。真正要建立的是整套安全认知:
- 怎么识别当前用户
- 怎么传递身份信息
- 怎么做接口访问控制
- 怎么设计角色与权限点
- 出问题时怎么排查
把 Authentication、Authorization、JWT、Claims、Role 和 Policy 这几块真正串起来之后,后续做管理系统、权限系统和企业后台项目时才会顺手很多。