.NET 认证与权限基础

系统讲解 ASP.NET Core 中的 Authentication、Authorization、JWT、Claims、角色与策略授权。

.NET 认证与权限基础

做后台系统或接口服务时,认证与权限几乎是绕不过去的主题。很多初学者能很快写出一个登录接口,但一到真正的权限控制场景就开始混乱:

  • 登录成功后怎么识别当前用户?
  • 接口到底应该返回 401 还是 403
  • RolePolicy 有什么区别?
  • 为什么 [Authorize] 加上了却不生效?

这些问题的根源,往往不是 API 不会写,而是没有把认证与授权的整体模型理解清楚。


第一章 先区分认证和授权

这是必须先分清楚的两个概念。

1.1 认证:你是谁

认证的目标是确认当前请求的身份。

例如:

  • 用户输入用户名密码登录
  • 系统校验通过
  • 系统确认“这是用户 Tom”

这就是认证。

1.2 授权:你能做什么

授权的目标是判断当前身份是否允许执行某个操作。

例如:

  • Tom 登录成功了
  • 但他没有管理员权限
  • 所以不能访问删除用户接口

这就是授权。

1.3 最常见的误区

很多人把“登录成功”直接等同于“有权限访问所有接口”,这是不对的。

正确顺序通常是:

  1. 先认证
  2. 再授权

第二章 ASP.NET Core 中和安全相关的核心概念

ASP.NET Core 里,认证授权通常围绕下面这些关键词展开:

  • Authentication
  • Authorization
  • Claims
  • Identity
  • Roles
  • Policies

可以先建立一个总模型:

登录
-> 服务端签发令牌 / 建立身份信息
-> 请求进入系统
-> Authentication 识别当前用户
-> Authorization 判断是否允许访问
-> 控制器 / 端点继续执行

第三章 JWT 为什么这么常见

在前后端分离项目里,最常见的接口鉴权方案就是 JWT

3.1 什么是 JWT

JWT 全称是 JSON Web Token。你可以把它理解成一个经过签名的身份票据,里面通常会携带:

  • 用户 ID
  • 用户名
  • 角色
  • 权限点
  • 过期时间

3.2 一个典型流程

  1. 用户提交用户名密码
  2. 服务端校验账号密码
  3. 校验通过后签发 JWT
  4. 客户端保存 Token
  5. 之后每次请求都带上 Authorization: Bearer <token>
  6. 服务端校验 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 登录接口的基本职责

通常包括:

  1. 接收账号密码
  2. 校验用户是否存在
  3. 校验密码是否正确
  4. 组装 Claims
  5. 生成 Token
  6. 返回给客户端

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,它的表达能力更强。


第十章 401403 到底怎么区分

这是权限排查时最常见的问题之一。

10.1 401 Unauthorized

通常表示:

  • 没带 Token
  • Token 无效
  • Token 过期
  • 签名校验失败

也就是说,问题通常出在“认证没通过”。

10.2 403 Forbidden

通常表示:

  • 已经认证成功
  • 但当前用户没有访问该资源的权限

也就是说,问题通常出在“授权没通过”。

10.3 一个简单记忆方式

  • 401:你是谁都没确认成功
  • 403:你是谁确认了,但你不能做这件事

第十一章 一个后台系统的常见权限设计思路

对于中后台系统,一个比较常见的做法是:

  1. 登录成功后签发 JWT
  2. 把用户标识、角色、权限点写入 Claims
  3. 接口层通过 [Authorize] + Policy 控制访问
  4. 前端根据权限点决定菜单和按钮是否展示

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 安全基础,建议至少掌握:

  1. 能清楚区分认证和授权
  2. 理解 JWT 的基本流程
  3. 会配置 AddAuthenticationAddAuthorization
  4. 知道 [Authorize][AllowAnonymous] 的用法
  5. 能理解 ClaimsRolePolicy 的区别
  6. 能快速区分 401403

掌握这些后,你就已经具备了做基础后台权限系统的安全基础。


下一步阅读


本文小结

.NET 中的认证与权限控制,并不只是“写一个登录接口,再给接口加个 [Authorize]”。真正要建立的是整套安全认知:

  • 怎么识别当前用户
  • 怎么传递身份信息
  • 怎么做接口访问控制
  • 怎么设计角色与权限点
  • 出问题时怎么排查

AuthenticationAuthorizationJWTClaimsRolePolicy 这几块真正串起来之后,后续做管理系统、权限系统和企业后台项目时才会顺手很多。