Asp Net Core - 使用 JWT 进行 Rest API 授权(角色、声明和策略)- 一步步

2025-06-11

Asp Net Core - 使用 JWT 进行 Rest API 授权(角色、声明和策略)- 一步步

在本文中,我们将介绍 AspNet Core 授权(角色、声明和策略)。分别介绍它们各自的适用场景,并帮助您更好地理解它们之间的协同作用。

今天我们要讨论的内容是:

  • 身份验证与授权
    • 什么是身份验证
    • 什么是授权
  • 授权类型
    • 什么是角色
    • 什么是索赔
    • 什么是政策
  • 原料
  • 代码和实现

您可以在 YouTube 上观看完整视频

您可以在 GitHub 上找到源代码
https://github.com/mohamadlawand087/v48-AspNetCore-Authorisation

这是 API 开发系列的第 4 部分,您可以通过以下链接查看各个部分:
第 1 部分:https://dev.to/moe23/asp-net-core-5-rest-api-step-by-step-2mb6
第 2 部分:https
://dev.to/moe23/asp-net-core-5-rest-api-authentication-with-jwt-step-by-step-140d 第 3 部分:https://dev.to/moe23/refresh-jwt-with-refresh-tokens-in-asp-net-core-5-rest-api-step-by-step-3en5

身份验证与授权

在我们深入探讨这个话题之前,尽管术语听起来相似,但身份验证和授权是登录过程中的独立步骤。

验证

身份验证是验证用户身份的行为。这是任何安全流程的第一步。

登录您的电子邮件或解锁您的手机是一种身份验证形式,您需要提供某种凭证,以便系统允许您进入并查看您的信息。

身份验证可以采取多种形式:

  • 密码。 用户名和密码是最常见的身份验证因素。如果用户输入正确的数据,系统将假定身份有效并授予访问权限。
  • 一次性密码。 仅授予一次会话或交易的访问权限。
  • 身份验证应用程序。 通过授予访问权限的外部方生成安全代码。
  • 生物识别技术。 用户通过指纹或眼部扫描来访问系统。

在某些情况下,系统要求成功验证多个因素才能授予访问权限。这种多因素身份验证 (MFA) 要求通常用于增强安全性,超越单靠密码所能提供的安全性。

图片描述

授权:

我们首先需要定义什么是身份验证,更重要的是,它不是什么。身份验证指的是确定用户能够做什么的过程。

换句话说,授权证明您有权提出请求。当您尝试进入音乐会或活动的后台时,您无需证明您的身份——只需出示门票即可,这证明您有权进入您想进入的地方。

授权与认证相互独立,但授权需要认证机制。

角色:

它们是一组在应用程序中执行某些活动的权限。我们可以将角色视为一个布尔值,表示我们是否拥有这个角色(true 或 false)。

图片描述

我们对角色的处理方式是,将功能赋予角色,一旦我们将用户分配给角色,这些功能集合就会被赋予该用户。一旦我们删除角色,这些功能也会被删除。

图片描述

角色将保护对功能的访问,如果用户没有正确的角色,则用户将无法执行该功能

声明:

它们与角色完全不同,基于声明的机制比角色更灵活,因为它们是键值对。声明属于用户或实体,用于描述用户或实体。声明本质上是用户属性,它们告知用户的授权。

图片描述

为了更好地说明,让我们再次检查一下驾驶执照示例

图片描述

我们可以看到,这个许可证上有 11 条声明,这意味着有 11 条关于用户的信息。如果我们想将其转换为基于代码的结构,它将如下所示



{
    "dl":"123456789",
    "exp":"07/11/2025",
    "ln":"DOE",
    "fn":"John",
  "dob":"09/05/1993",
  "sex":"M",
    "hair":"brn",
    "eyes":"blue",
    "hgt":"6.0"
    "wgt":"183lb",
  "class":"C"
}


Enter fullscreen mode Exit fullscreen mode

因此,用户登录后就会收到这些声明。

根据我们希望如何实现授权流程,声明可以与角色一起工作,也可以与角色不一起工作。

政策:

它们是用于检查用户信息并检查是否授予或拒绝权限的功能或规则。

图片描述

策略基本上从上下文开始,根据策略列表检查用户,并根据列表授予或拒绝对所请求资源的权限。

基于角色的授权和基于声明的授权使用需求、需求处理程序和预配置策略。策略由一个或多个需求组成

图片描述

角色、声明和策略

角色是一种符号类别,用于将具有相同安全权限级别的用户集合在一起。基于角色的授权需要首先识别用户,然后确定用户被分配的角色,最后将这些角色与有权访问资源的角色进行比较。

相反,主张不是基于群体,而是基于身份。

代码

我们将继续构建上一个使用 JWT 令牌授权的项目,您可以在 github 上找到源代码

https://github.com/mohamadlawand087/v8-refreshtokenswithJWT

一旦我们克隆了这个 repo,我们就可以开始构建我们的授权

我们要做的第一件事是更新启动类,使其包含 Roles 到身份提供者中。在 Startup 类的 configureServices 中,我们需要更新以下内容



services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
                        .AddEntityFrameworkStores<ApiDbContext>();


Enter fullscreen mode Exit fullscreen mode

然后我们需要做的是在控制器文件夹内创建一个名为 SetupController 的新控制器,并添加以下内容



[Route("api/[controller]")] // api/setup
    [ApiController]
    public class SetupController: ControllerBase
    {
        private readonly ApiDbContext _context;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly UserManager<IdentityUser> _userManager;
        protected readonly ILogger<SetupController> _logger;

        public SetupController(
            ApiDbContext context,
            RoleManager<IdentityRole> roleManager,
            UserManager<IdentityUser> userManager,
            ILogger<SetupController> logger)
        {   
            _logger = logger;
            _roleManager = roleManager;
            _userManager = userManager;
            _context = context;
        }

        [HttpGet]
        public IActionResult GetAllRoles()
        {
            var roles = _roleManager.Roles.ToList();
            return Ok(roles);
        }

        [HttpPost]
        public async Task<IActionResult> CreateRole(string roleName)
        {
            var roleExist = await _roleManager.RoleExistsAsync(roleName);
            if (!roleExist) {
                //create the roles and seed them to the database: Question 1
                var roleResult = await _roleManager.CreateAsync (new IdentityRole (roleName));

                if (roleResult.Succeeded) {
                    _logger.LogInformation (1, "Roles Added");
                    return Ok(new {result = $"Role {roleName} added successfully"});
                } else {
                    _logger.LogInformation (2, "Error");
                    return BadRequest(new {error = $"Issue adding the new {roleName} role"});
                }
            }

            return BadRequest(new {error = "Role already exist"});
        }

        // Get all users
        [HttpGet]
        [Route("GetAllUsers")]
        public async Task<IActionResult> GetAllUsers()
        {
            var users = await _userManager.Users.ToListAsync();
            return Ok(users);
        }

        // Add User to role
        [HttpPost]
        [Route("AddUserToRole")]
        public async Task<IActionResult> AddUserToRole(string email, string roleName)
        {
            var user = await _userManager.FindByEmailAsync(email);

            if(user != null)
            {
                var result = await _userManager.AddToRoleAsync(user, roleName);

                if(result.Succeeded)
                {
                    _logger.LogInformation (1, $"User {user.Email} added to the {roleName} role");
                    return Ok(new {result = $"User {user.Email} added to the {roleName} role"});
                }
                else
                {
                    _logger.LogInformation (1, $"Error: Unable to add user {user.Email} to the {roleName} role");
                    return BadRequest(new {error = $"Error: Unable to add user {user.Email} to the {roleName} role"});
                }
            }

            // User doesn't exist
            return BadRequest(new {error = "Unable to find user"});
        }

        // Get specific user role
        [HttpGet]
        [Route("GetUserRoles")]
        public async Task<IActionResult> GetUserRoles(string email)
        {
            // Resolve the user via their email
            var user = await _userManager.FindByEmailAsync(email);
            // Get the roles for the user
            var roles = await _userManager.GetRolesAsync(user);
            return Ok(roles);
        }

        // Remove User to role
        [HttpPost]
        [Route("RemoveUserFromRole")]
        public async Task<IActionResult> RemoveUserFromRole(string email, string roleName)
        {
            var user = await _userManager.FindByEmailAsync(email);

            if(user != null)
            {
                var result = await _userManager.RemoveFromRoleAsync(user, roleName);

                if(result.Succeeded)
                {
                    _logger.LogInformation (1, $"User {user.Email} removed from the {roleName} role");
                    return Ok(new {result = $"User {user.Email} removed from the {roleName} role"});
                }
                else
                {
                    _logger.LogInformation (1, $"Error: Unable to removed user {user.Email} from the {roleName} role");
                    return BadRequest(new {error = $"Error: Unable to removed user {user.Email} from the {roleName} role"});
                }
            }

            // User doesn't exist
            return BadRequest(new {error = "Unable to find user"});
        }
    }


Enter fullscreen mode Exit fullscreen mode

完成 SetupController 后,让我们转到 AuthManagement Controller 并更新以下内容



// We need to add the following before the constructor
private readonly RoleManager<IdentityRole> _roleManager;
protected readonly ILogger<AuthManagementController> _logger;

// We need to update the constructor to the following
public AuthManagementController(
          UserManager<IdentityUser> userManager,
          RoleManager<IdentityRole> roleManager,
          IOptionsMonitor<JwtConfig> optionsMonitor,
          TokenValidationParameters tokenValidationParams,
          ILogger<AuthManagementController> logger,
          ApiDbContext apiDbContext)
{
    _logger = logger;
    _userManager = userManager;
    _roleManager = roleManager;
    _jwtConfig = optionsMonitor.CurrentValue;
    _tokenValidationParams = tokenValidationParams;
    _apiDbContext = apiDbContext;
}

// We need to create a GetValidClaims method
private async Task<List<Claim>> GetValidClaims(IdentityUser user)
{
    IdentityOptions _options = new IdentityOptions();
    var claims = new List<Claim>
    {
        new Claim("Id", user.Id), 
        new Claim(JwtRegisteredClaimNames.Email, user.Email),
        new Claim(JwtRegisteredClaimNames.Sub, user.Email),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
        new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName),
    };

    var userClaims = await _userManager.GetClaimsAsync(user);
    var userRoles = await _userManager.GetRolesAsync(user);
    claims.AddRange(userClaims);
    foreach (var userRole in userRoles)
    {
        claims.Add(new Claim(ClaimTypes.Role, userRole));
        var role = await _roleManager.FindByNameAsync(userRole);
        if(role != null)
        {
            var roleClaims = await _roleManager.GetClaimsAsync(role);
            foreach(Claim roleClaim in roleClaims)
            {
                claims.Add(roleClaim);
            }
        }
    }
    return claims;
}

// We need to update the GenerateJwtToken method
var claims = await GetValidClaims(user);

var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(claims),
    Expires = DateTime.UtcNow.AddMinutes(5), // 5-10 
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};


Enter fullscreen mode Exit fullscreen mode

接下来我们需要更新 TodoController 属性以向其添加角色



[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "AppUser")]


Enter fullscreen mode Exit fullscreen mode

现在让我们尝试一下

  • 将创建一个新用户
  • 将创建一个名为 AppUser 的角色
  • 将角色分配给用户
  • 将登录并获取 JWT 令牌
  • 将尝试访问 GetItems 端点

现在我们开始添加 ClaimSetup 控制器,在控制器文件夹中添加一个名为 ClaimSetupController 的新类,并添加以下内容



[Route("api/[controller]")] // api/ClaimSetup
[ApiController]
public class ClaimSetupController : ControllerBase
{
    private readonly ApiDbContext _context;
    private readonly RoleManager<IdentityRole> _roleManager;
    private readonly UserManager<IdentityUser> _userManager;
    protected readonly ILogger<ClaimSetupController> _logger;

    public ClaimSetupController(
        ApiDbContext context,
        RoleManager<IdentityRole> roleManager,
        UserManager<IdentityUser> userManager,
        ILogger<ClaimSetupController> logger)
    {   
        _logger = logger;
        _roleManager = roleManager;
        _userManager = userManager;
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> GetAllClaims(string email)
    {
        var user = await _userManager.FindByEmailAsync(email);

        var claims = await _userManager.GetClaimsAsync(user);

        return Ok(claims);
    }

    // Add Claim to user
    [HttpPost]
    [Route("AddClaimToUser")]
    public async Task<IActionResult> AddClaimToUser(string email, string claimName, string value)
    {
        var user = await _userManager.FindByEmailAsync(email);

        var userClaim = new Claim(claimName, value);

        if(user != null)
        {
            var result = await _userManager.AddClaimAsync(user, userClaim);

            if(result.Succeeded)
            {
                _logger.LogInformation (1, $"the claim {claimName} add to the  User {user.Email}");
                return Ok(new {result = $"the claim {claimName} add to the  User {user.Email}"});
            }
            else
            {
                _logger.LogInformation (1, $"Error: Unable to add the claim {claimName} to the  User {user.Email}");
                return BadRequest(new {error = $"Error: Unable to add the claim {claimName} to the  User {user.Email}"});
            }
        }

        // User doesn't exist
        return BadRequest(new {error = "Unable to find user"});
    }
}


Enter fullscreen mode Exit fullscreen mode

现在我们需要更新 Startup 类来创建一个声明策略,在根目录下的 Startup.cs 中,我们需要在 configureServices 方法中添加以下内容



services.AddAuthorization(options =>
  {
      options.AddPolicy("ViewItemsPolicy",
          policy => policy.RequireClaim("ViewItems"));
  });


Enter fullscreen mode Exit fullscreen mode

接下来,我们需要根据我们想要的操作更新 TodoController



[HttpGet]
[Authorize(Policy = "ViewItemsPolicy")]
public async Task<IActionResult> GetItems()
{
    var items = await _context.Items.ToListAsync();
    return Ok(items);
}


Enter fullscreen mode Exit fullscreen mode

现在让我们测试一下

鏂囩珷鏉ユ簮锛�https://dev.to/moe23/asp-net-core-rest-api-authorization-with-jwt-roles-vs-claims-vs-policy-step-by-step-5bgn
PREV
MongoDB 模式设计模式(一)
NEXT
如何创建简单的 CI/CD 管道