ASP.NET Core 中JWT的基本使用

文章目录


前言

ASP.NET Core Web API 中实现基于JWTRBAC(基于角色的访问控制)。

一、JWT与RBAC

JWT (JSON Web Token)与 RBAC (基于角色的访问控制,Role-Based Access Access Control)结合使用时,是一种通过令牌(Token)传递用户角色信息,并基于角色实现权限管理的安全机制。

二、JWT 的作用

  • JWT 是一种紧凑的、自包含的令牌格式,通常用于身份验证和授权。其结构分为三部分:
    • Header :算法和令牌类型(如 HS256RSA)。
    • Payload:存放用户信息(如用户ID、角色)和声明(如过期时间)。
    • Signature:对前两部分的签名,确保令牌未被篡改。

三、RBAC 的核心思想

  • RBAC 通过角色管理权限,而非直接赋予用户权限:
    • 角色(Role) :定义一组权限(如 adminuser)。
    • 权限(Permission) :资源或操作(如 read:data、delete:user)。
    • 用户被分配角色,间接获得权限。

四、使用

1、配置文件 (appsettings.json)

  1. appsettings.json

    csharp 复制代码
    {
      "JwtSettings": {
    	  "Issuer": "yourdomain.com",
    	  "Audience": "yourapp",
    	  "SecretKey": "YourSuperSecretKeyAtLeast32CharactersLong",
    	  "ExpirationMinutes": 60,
    	  "RefreshTokenExpirationDays": 7
    	}
      "AllowedHosts": "*"
    }

2、JWT配置模型 (Entity/JwtSettings.cs)

  1. JwtSettings.cs

    csharp 复制代码
    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)

  1. ServiceExtensions.cs

    csharp 复制代码
    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、用户仓库接口服务

  1. Interfaces/IUserRepository.cs

    csharp 复制代码
    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);
        }
    }
  2. Repositories/UserRepository.cs

    csharp 复制代码
    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)

  1. IAuthService.cs

    csharp 复制代码
    using JWTWebAPI.Entity;
    
    namespace JWTWebAPI.Interface
    {
        public interface IAuthService
        {
            Task<AuthResult> Authenticate(string username, string password);
            Task<AuthResult> RefreshToken(string token, string refreshToken);
        }
    }
  2. AuthService.cs

    csharp 复制代码
    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);
            }
        }
    }
  3. AuthResult.cs

    csharp 复制代码
    namespace JWTWebAPI.Entity
    {
        public class AuthResult
        {
            public string Token { get; set; }
            public string RefreshToken { get; set; }
            public int ExpiresIn { get; set; }
        }
    }

6、控制器(验证登录权限,生成Token)

  1. AuthController.cs

    csharp 复制代码
    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);
            }
        }
    } 
  2. LoginModel.cs

    csharp 复制代码
    namespace JWTWebAPI.Entity
    {
        public class LoginModel
        {
            public string Username { get; set; } = string.Empty;
            public string Password { get; set; } = string.Empty;
        }
    }
  3. RefreshTokenRequest.cs

    csharp 复制代码
    public class RefreshTokenRequest
    {
        [Required] public string Token { get; set; }
        [Required] public string RefreshToken { get; set; }
    }

7、注册服务

  1. Program.cs

    csharp 复制代码
    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、使用示例

  1. AdminController.cs
    在需要权限的Controller上添加特性:

    csharp 复制代码
    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)实现自定义策略处理器:

  1. PermissionHandler.cs

    csharp 复制代码
    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;
            }
        }
    }
  2. PermissionRequirement.cs

    csharp 复制代码
    using Microsoft.AspNetCore.Authorization;
    
    namespace JWTWebAPI.Entity
    {
        public class PermissionRequirement: IAuthorizationRequirement
        {
            public string Permission { get; set; }
    
            public PermissionRequirement(string permission)
            {
                Permission = permission;
            }
        }
    }

2)注册服务及定义策略

  1. Program.cs

    csharp 复制代码
    //注册
    builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
    //定义策略(可放在扩展方法中:ServiceExtensions.cs中的ConfigureAuthorizationPolicies方法中)
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("UpdateValidate", policy =>
            policy.Requirements.Add(new PermissionRequirement("profile.update")));
    });

3)使用自定义策略

  1. AdminController.cs

    csharp 复制代码
    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环境变量存储密钥
    csharp 复制代码
    builder.Configuration.AddEnvironmentVariables();
  • HTTPS重定向

    csharp 复制代码
    app.UseHttpsRedirection();
  • CORS配置

    csharp 复制代码
    services.AddCors(options =>
    {
        options.AddPolicy("ApiPolicy", builder =>
        {
            builder.WithOrigins("https://your-frontend.com")
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
        });
    });

六、完整流程说明

  1. 用户通过/api/auth/login获取JWT Token
  2. 后续请求在Header中添加Authorization: Bearer {token}
  3. 系统自动验证Token有效性
  4. 根据Controller/Action上的授权策略进行权限验证

总结

通过以上实现,可以构建灵活、可扩展的权限控制系统,满足以下场景:

  • 基于资源的细粒度控制
  • 动态权限管理
  • 多条件组合授权
相关推荐
Java中文社群16 分钟前
面试官:如何实现大模型连续对话?
java·后端·面试
CodeSaku17 分钟前
7大设计原则学习笔记
java·后端
小沈同学呀27 分钟前
Spring Boot 中 META-INF 的作用与功能详解
java·spring boot·后端
超浪的晨30 分钟前
Java 集合框架详解:Collection 接口全解析,从基础到实战
java·开发语言·后端·学习·个人开发
泉城老铁1 小时前
SpringBoot集成EasyPoi,实现Excel模板导出成PDF文件
后端
程序视点1 小时前
Microsoft .Net 运行库离线合集包专业解析
前端·后端·.net
欲儿1 小时前
LiteCloud超轻量级网盘项目基于Spring Boot
java·spring boot·后端·轻量级网盘项目
麦麦麦造1 小时前
一键把网页转成 LLM 友好格式的工具?
后端·python
想当花匠的小码农1 小时前
golang 项目 OpenTelemetry 实践
后端
Jiude1 小时前
如何使用 Certbot 为域名配置永久免费的 HTTPS 证书
后端·nginx·https