文章目錄
- 前言
- 一、JWT與RBAC
- 二、JWT 的作用
- 三、RBAC 的核心思想
- 四、使用
- 1、配置文件 (appsettings.json)
- 2、JWT配置模型 (Entity/JwtSettings.cs)
- 3、服務擴展類,JWT配置 (Extensions/ServiceExtensions.cs)
- 4、用戶倉庫接口服務
- 5、認證服務 (Interface/IAuthService.cs、Repository/AuthService.cs)
- 6、控制器(驗證登錄權限,生成Token)
- 7、注冊服務
- 8、使用示例
- 9、高級權限控制
- 1)實現自定義策略處理器:
- 2)注冊服務及定義策略
- 3)使用自定義策略
- 五、關鍵安全配置
- 六、完整流程說明
- 總結
前言
在ASP.NET Core Web API中實現基于JWT的RBAC(基于角色的訪問控制)。
一、JWT與RBAC
JWT(JSON Web Token)與 RBAC(基于角色的訪問控制,Role-Based Access Access Control)結合使用時,是一種通過令牌(Token)傳遞用戶角色信息,并基于角色實現權限管理的安全機制。
二、JWT 的作用
- JWT 是一種緊湊的、自包含的令牌格式,通常用于身份驗證和授權。其結構分為三部分:
- Header:算法和令牌類型(如 HS256 或 RSA)。
- Payload:存放用戶信息(如用戶ID、角色)和聲明(如過期時間)。
- Signature:對前兩部分的簽名,確保令牌未被篡改。
三、RBAC 的核心思想
- RBAC 通過角色管理權限,而非直接賦予用戶權限:
- 角色(Role):定義一組權限(如 admin、user)。
- 權限(Permission):資源或操作(如 read:data、delete:user)。
- 用戶被分配角色,間接獲得權限。
四、使用
1、配置文件 (appsettings.json)
- appsettings.json
{"JwtSettings": {"Issuer": "yourdomain.com","Audience": "yourapp","SecretKey": "YourSuperSecretKeyAtLeast32CharactersLong","ExpirationMinutes": 60,"RefreshTokenExpirationDays": 7}"AllowedHosts": "*" }
2、JWT配置模型 (Entity/JwtSettings.cs)
- JwtSettings.cs
namespace JWTWebAPI.Entity {public class JwtSettings{public string Issuer { get; set; } = string.Empty;public string Audience { get; set; } = string.Empty;public string SecretKey { get; set; } = string.Empty;public int ExpirationMinutes { get; set; }public int RefreshTokenExpirationDays { get; set; }} }
3、服務擴展類,JWT配置 (Extensions/ServiceExtensions.cs)
- ServiceExtensions.cs
using JWTWebAPI.Entity; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; using System.Text;namespace JWTWebAPI.Extensions {public static class ServiceExtensions{// JWT認證配置public static void ConfigureJwtAuthentication(this IServiceCollection services, IConfiguration configuration){var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>();services.AddAuthentication(options =>{options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(options =>{options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = false,ValidIssuer = jwtSettings.Issuer,ValidateAudience = false,ValidAudience = jwtSettings.Audience,ValidateLifetime = false,ValidateIssuerSigningKey = false,IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey!)), //ClockSkew = TimeSpan.Zero,RoleClaimType=ClaimTypes.Role};options.Events = new JwtBearerEvents{OnAuthenticationFailed = context =>{if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)){context.Response.Headers.Append("Token-Expired", "true");}return Task.CompletedTask;}};});}// 授權策略配置public static void ConfigureAuthorizationPolicies(this IServiceCollection services){services.AddAuthorization(options =>{// 基于角色的策略options.AddPolicy("AdminOnly", policy =>policy.RequireRole("admin"));options.AddPolicy("ManagerOnly", policy =>policy.RequireRole("admin"));// 基于自定義權限的策略options.AddPolicy("ContentEditor", policy =>policy.RequireClaim("permission", "content.edit"));options.AddPolicy("UserManagement", policy =>policy.RequireClaim("permission", "user.manage"));});}} }
4、用戶倉庫接口服務
-
Interfaces/IUserRepository.cs
using JWTWebAPI.Entity;namespace JWTWebAPI.Interface {public interface IUserRepository{Task<AspNetUsers?> GetUserByCredentials(string username, string password);Task SaveRefreshToken(long userId, string refreshToken, DateTime expiry);Task<AspNetUsers?> GetUserByRefreshToken(string refreshToken);} }
-
Repositories/UserRepository.cs
using JWTWebAPI.Data; using JWTWebAPI.Entity; using JWTWebAPI.Interface; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Org.BouncyCastle.Crypto.Generators;namespace JWTWebAPI.Repository {public class UserRepositor : IUserRepository{private readonly MyDbContext _context;private readonly UserManager<AspNetUsers> _userManager;public UserRepositor(MyDbContext context, UserManager<AspNetUsers> userManager, RoleManager<Role> roleManager){_context = context;_userManager = userManager;}public async Task<AspNetUsers?> GetUserByCredentials(string username, string password){try{var user=await _userManager.FindByNameAsync(username);if (user == null) return null;return user;}catch (Exception){throw;}}public async Task<AspNetUsers?> GetUserByRefreshToken(string refreshToken){return await _context.Users.FirstOrDefaultAsync(u =>u.RefreshToken == refreshToken &&u.RefreshTokenExpiry > DateTime.UtcNow);}public async Task SaveRefreshToken(long userId, string refreshToken, DateTime expiry){var user = await _context.Users.FindAsync(userId);if (user != null){user.RefreshToken = refreshToken;user.RefreshTokenExpiry = expiry;await _context.SaveChangesAsync();}}} }
5、認證服務 (Interface/IAuthService.cs、Repository/AuthService.cs)
-
IAuthService.cs
using JWTWebAPI.Entity;namespace JWTWebAPI.Interface {public interface IAuthService{Task<AuthResult> Authenticate(string username, string password);Task<AuthResult> RefreshToken(string token, string refreshToken);} }
-
AuthService.cs
using JWTWebAPI.Entity; using JWTWebAPI.Interface; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text;namespace JWTWebAPI.Repository {public class AuthService : IAuthService{private readonly JwtSettings _jwtSettings;private readonly IUserRepository _userRepository;public AuthService(IOptions<JwtSettings> jwtSettings, IUserRepository userRepository){_jwtSettings = jwtSettings.Value;_userRepository = userRepository;}public async Task<AuthResult> Authenticate(string username, string password){var user = await _userRepository.GetUserByCredentials(username, password);if (user == null) return null;var claims = new[]{new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),new Claim(ClaimTypes.Name, user.UserName),new Claim(ClaimTypes.Role, user.Role) // 用戶角色 };var token = GenerateJwtToken(claims);var refreshToken = GenerateRefreshToken();await _userRepository.SaveRefreshToken(user.Id, refreshToken,DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays));return new AuthResult{Token = token,RefreshToken = refreshToken,ExpiresIn = _jwtSettings.ExpirationMinutes * 60};}public Task<AuthResult> RefreshToken(string token, string refreshToken){throw new NotImplementedException();}private string GenerateJwtToken(IEnumerable<Claim> claims){var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);var token = new JwtSecurityToken(issuer: _jwtSettings.Issuer,audience: _jwtSettings.Audience,claims: claims,expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),signingCredentials: creds);return new JwtSecurityTokenHandler().WriteToken(token);}private static string GenerateRefreshToken(){var randomNumber = new byte[32];using var rng = RandomNumberGenerator.Create();rng.GetBytes(randomNumber);return Convert.ToBase64String(randomNumber);}} }
-
AuthResult.cs
namespace JWTWebAPI.Entity {public class AuthResult{public string Token { get; set; }public string RefreshToken { get; set; }public int ExpiresIn { get; set; }} }
6、控制器(驗證登錄權限,生成Token)
- AuthController.cs
using JWTWebAPI.Entity; using JWTWebAPI.Interface; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Runtime; using System.Security.Claims; using System.Text;namespace JWTWebAPI.Controllers {[Route("api/[controller]/[action]")][ApiController]public class AuthController : ControllerBase{private readonly IConfiguration _config;private readonly IOptionsSnapshot<JwtSettings> _settings;private readonly IAuthService _authService;public AuthController(IConfiguration config, IOptionsSnapshot<JwtSettings> settings, IAuthService authService){_config = config;_settings = settings;_authService = authService;}[HttpPost][AllowAnonymous]public async Task<IActionResult> Login([FromBody] LoginModel request){var result = await _authService.Authenticate(request.Username, request.Password);if (result == null) return Unauthorized();return Ok(result);}[HttpPost][AllowAnonymous]public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest request){var result = await _authService.RefreshToken(request.Token, request.RefreshToken);if (result == null) return Unauthorized();return Ok(result);}} }
- LoginModel.cs
namespace JWTWebAPI.Entity {public class LoginModel{public string Username { get; set; } = string.Empty;public string Password { get; set; } = string.Empty;} }
- RefreshTokenRequest.cs
public class RefreshTokenRequest {[Required] public string Token { get; set; }[Required] public string RefreshToken { get; set; } }
7、注冊服務
-
Program.cs
using JWTWebAPI.Data; using JWTWebAPI.Entity; using JWTWebAPI.Extensions; using JWTWebAPI.Interface; using JWTWebAPI.Repository; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore;var builder = WebApplication.CreateBuilder(args); // 添加數據庫上下文 var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<MyDbContext>(opt => {opt.UseSqlServer(connectionString); }); builder.Services.AddDataProtection(); builder.Services.AddIdentityCore<AspNetUsers>(opt => { opt.Lockout.MaxFailedAccessAttempts = 5;//登錄失敗多少次賬號被鎖定 opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);//鎖定多長時間 opt.Password.RequireDigit = false;//密碼是否需要數字 opt.Password.RequireLowercase = false;//密碼是否需要小寫字符 opt.Password.RequireUppercase = false;//密碼是否需要大寫字符 opt.Password.RequireNonAlphanumeric = false;//密碼是否需要非字母數字的字符 opt.Password.RequiredLength = 6;//密碼長度opt.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;//密碼重置令牌,使用默認的郵箱令牌提供程序來生成和驗證令牌。此提供程序通常與用戶郵箱關聯,生成的令牌會通過郵件發送給用戶,保證用戶通過郵件接收密碼重置鏈接。 opt.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;//配置郵箱確認令牌(Email Confirmation Token)的生成和驗證所使用的提供程序(Provider) }); var idBuilder = new IdentityBuilder(typeof(AspNetUsers), typeof(Role), builder.Services);idBuilder.AddEntityFrameworkStores<MyDbContext>() .AddDefaultTokenProviders().AddUserManager<UserManager<AspNetUsers>>() .AddRoleManager<RoleManager<Role>>();builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings")); // 添加JWT認證 // 3. 認證服務配置(來自ServiceExtensions) builder.Services.ConfigureJwtAuthentication(builder.Configuration); // 擴展方法ServiceExtensions.cs // 4. 授權策略配置(來自ServiceExtensions) builder.Services.ConfigureAuthorizationPolicies(); // 擴展方法ServiceExtensions.cs// 5. 注冊應用服務 builder.Services.AddScoped<IUserRepository, UserRepositor>(); builder.Services.AddScoped<IAuthService, AuthService>();builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //跨域 builder.Services.AddCors(options => {options.AddPolicy("AllowAll", builder =>builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); });var app = builder.Build();// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) {app.UseSwagger();app.UseSwaggerUI(); }app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseCors("AllowAll"); app.MapControllers();app.Run();
8、使用示例
- AdminController.cs
在需要權限的Controller上添加特性:using JWTWebAPI.Entity; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc;namespace JWTWebAPI.Controllers {[Route("api/[controller]/[action]")][ApiController]public class AdminController : ControllerBase{[HttpGet][Authorize(Roles = "admin")] // 僅Admin角色可訪問public IActionResult GetAdminData(){return Ok("Admin data");}[HttpGet][Authorize(Policy = "AdminOnly")] // 使用策略public IActionResult GetUserInfo(){return Ok("User info");}} }
9、高級權限控制
1)實現自定義策略處理器:
-
PermissionHandler.cs
using JWTWebAPI.Entity; using Microsoft.AspNetCore.Authorization;namespace JWTWebAPI.Extensions {public class PermissionHandler : AuthorizationHandler<PermissionRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement){var permissions = context.User.FindFirst(c => c.Type == "permissions")?.Value.Split(',');if (permissions?.Any(p => p == requirement.Permission) == true){context.Succeed(requirement);}return Task.CompletedTask;}} }
-
PermissionRequirement.cs
using Microsoft.AspNetCore.Authorization;namespace JWTWebAPI.Entity {public class PermissionRequirement: IAuthorizationRequirement{public string Permission { get; set; }public PermissionRequirement(string permission){Permission = permission;}} }
2)注冊服務及定義策略
- Program.cs
//注冊 builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); //定義策略(可放在擴展方法中:ServiceExtensions.cs中的ConfigureAuthorizationPolicies方法中) builder.Services.AddAuthorization(options => {options.AddPolicy("UpdateValidate", policy =>policy.Requirements.Add(new PermissionRequirement("profile.update"))); });
3)使用自定義策略
- AdminController.cs
using JWTWebAPI.Entity; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc;namespace JWTWebAPI.Controllers {[Route("api/[controller]/[action]")][ApiController]public class AdminController : ControllerBase{[HttpGet][Authorize(Policy = "UpdateValidate")]public IActionResult GetMessage(){return Ok("自定義驗證");}} }
五、關鍵安全配置
- 密鑰安全:
- 生產環境不要將密鑰放在appsettings.json中
- 使用Azure Key Vault或環境變量存儲密鑰
builder.Configuration.AddEnvironmentVariables();
- HTTPS重定向:
app.UseHttpsRedirection();
- CORS配置:
services.AddCors(options => {options.AddPolicy("ApiPolicy", builder =>{builder.WithOrigins("https://your-frontend.com").AllowAnyHeader().AllowAnyMethod().AllowCredentials();}); });
六、完整流程說明
- 用戶通過/api/auth/login獲取JWT Token
- 后續請求在Header中添加Authorization: Bearer {token}
- 系統自動驗證Token有效性
- 根據Controller/Action上的授權策略進行權限驗證
總結
通過以上實現,可以構建靈活、可擴展的權限控制系統,滿足以下場景:
- 基于資源的細粒度控制
- 動態權限管理
- 多條件組合授權