使用 JWT 进行 ASP.NET Core Web API 的身份验证与角色授权
引言
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息,信息以 JSON 对象的形式呈现。由于 JWT 是数字签名的,因此可以被验证和信任。JWT 可以使用密钥(HMAC 算法)或公钥/私钥对(RSA 或 ECDSA)进行签名。
在本文中,我们将讨论以下内容:
-
ASP.NET Core Web API 的身份验证 -
使用 JWT 令牌 -
基于角色的授权 -
使用 ASP.NET 的低级身份验证功能,而不使用 ASP.NET Core Identity -
我们将遵循仓库模式进行实现
准备工作
在开始之前,请确保您已安装以下工具:
-
.NET Core 6 SDK -
Visual Studio 2022 -
Postman
步骤实现
一. 创建新的 .NET Core 6.0 Web API 项目
-
打开 Visual Studio,选择“新建项目”。 -
选择“ASP.NET Core Web 应用”。 -
选择“API”模板,并确保目标框架设置为 .NET 6.0。
二. 项目结构
创建完成后,您的项目结构应该如下所示:
- Custom_Jwt_Token_Example
- Controllers
- Helper
- Models
- Services
- appsettings.json
三. 安装 JWT 包
在项目中打开 NuGet 包管理控制台,输入以下命令安装 JWT 相关库:
Install-Package System.IdentityModel.Tokens.Jwt
四. 通过 Web API 端点进行用户身份验证
接下来,我们需要通过询问凭据来验证用户,生成令牌并将其返回给 API 客户端。这通常发生在控制器的操作方法或中间件端点处理程序中。
using Custom_Jwt_Token_Example.Models;
using Custom_Jwt_Token_Example.Services;
using Microsoft.AspNetCore.Mvc;
namespace Custom_Jwt_Token_Example.Controllers
{
[Route("api/[controller]")]
public class AuthenticationController : Controller
{
private readonly IAuthenticationService authenticationService;
public AuthenticationController(IAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
[HttpPost]
[Route("Login")]
public AuthenticateResponse Login(AuthenticateRequest model)
{
return this.authenticationService.Authenticate(model);
}
}
}
五. 实现仓库接口
创建 IAuthentication
接口,作为身份验证功能的抽象层。
using Custom_Jwt_Token_Example.Models;
namespace Custom_Jwt_Token_Example.Services
{
public interface IAuthenticationService
{
AuthenticateResponse Authenticate(AuthenticateRequest model);
}
}
创建 IUserService
接口,作为用户功能的抽象层。
using Custom_Jwt_Token_Example.Models;
namespace Custom_Jwt_Token_Example.Services
{
public interface IUserService
{
User GetById(int id);
IEnumerable<User> GetAll();
}
}
六. 用户服务实现
使用 UserService
类的 GetById
方法来获取用户详细信息。
using Custom_Jwt_Token_Example.Models;
namespace Custom_Jwt_Token_Example.Services
{
public class UserService : IUserService
{
private List<User> _users = new List<User> {
new User {
Id = 1, FirstName = "mytest", Role = new List<Role>{ Role.Customer }, LastName = "User", Username = "mytestuser", Password = "test123"
},
new User {
Id = 2, FirstName = "mytest2", LastName = "User2", Username = "test", Password = "test"
}
};
public IEnumerable<User> GetAll()
{
return _users;
}
public User GetById(int id)
{
return _users.FirstOrDefault(x => x.Id == id);
}
}
}
七. 实现身份验证服务
创建 AuthenticationService
类来实现 IAuthenticationService
接口。
using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Models;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Custom_Jwt_Token_Example.Services
{
public class AuthenticationService : IAuthenticationService
{
private List<User> _users = new List<User> {
new User {
Id = 1, FirstName = "mytest", LastName = "User", Username = "mytestuser", Role = new List<Role>{ Role.Customer }, Password = "test123"
}
};
private readonly AppSettings _appSettings;
public AuthenticationService(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
{
var user = _users.SingleOrDefault(x => x.Username == model.UserName && x.Password == model.Password);
if (user == null) return null;
var token = generateToken(user);
return new AuthenticateResponse() { Token = token };
}
private string generateToken(User user)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key));
var credential = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
List<Claim> claims = new List<Claim>()
{
new Claim("Id", Convert.ToString(user.Id)),
new Claim(JwtRegisteredClaimNames.Sub, user.Username),
new Claim(JwtRegisteredClaimNames.Email, "test@gmail.com"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
foreach (var role in user.Role)
{
claims.Add(new Claim("Role", Convert.ToString(role)));
}
var token = new JwtSecurityToken(_appSettings.Issuer, _appSettings.Issuer, claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credential);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
八. 创建中间件
接下来,我们创建一个中间件组件,用于从请求头中获取令牌,验证令牌并将其附加到用户上下文中。
using Custom_Jwt_Token_Example.Services;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
namespace Custom_Jwt_Token_Example.Helper
{
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly AppSettings _appSettings;
public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
{
this._next = next;
this._appSettings = appSettings.Value;
}
public async Task Invoke(HttpContext context, IUserService userService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
attachUserToContext(context, userService, token);
await _next(context);
}
private void attachUserToContext(HttpContext context, IUserService userService, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key));
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ClockSkew = TimeSpan.Zero,
ValidIssuer = _appSettings.Issuer,
ValidAudience = _appSettings.Issuer
}, out SecurityToken validateToken);
var jwtToken = (JwtSecurityToken)validateToken;
var userId = int.Parse(jwtToken.Claims.FirstOrDefault(_ => _.Type == "Id").Value);
context.Items["User"] = userService.GetById(userId);
}
catch
{
// 处理异常
}
}
}
}
九. 授权装饰器
在 API 端点上添加 [Authorization] 特性,每次调用 API 端点之前,都会调用 OnAuthorization
方法。
using Custom_Jwt_Token_Example.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Custom_Jwt_Token_Example.Helper
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class Authorization : Attribute, IAuthorizationFilter
{
private readonly IList<Role> _roles;
public Authorization(params Role[] roles)
{
this._roles = roles ?? new Role[] { };
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isRolePermission = false;
User user = (User)context.HttpContext.Items["User"];
if (user == null)
{
context.Result = new JsonResult(new { Message = "Unauthorized" })
{ StatusCode = StatusCodes.Status401Unauthorized };
}
else if (this._roles.Any())
{
foreach (var userRole in user.Role)
{
foreach (var authRole in this._roles)
{
if (userRole == authRole)
{
isRolePermission = true;
}
}
}
if (!isRolePermission)
context.Result = new JsonResult(new { Message = "Unauthorized" })
{ StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
}
十. 装饰 API 端点
使用 [Authorization(Role.Customer)] 特性装饰 API 端点。
using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Models;
using Microsoft.AspNetCore.Mvc;
namespace Custom_Jwt_Token_Example.Controllers
{
[Authorization(Role.Customer)]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[] {
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
十一. 配置 Appsettings.json
在 appsettings.json
文件中添加 JWT 令牌密钥和发行者部分。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
"Key": "986ghgrgtru989ASdsaerew13434545435",
"Issuer": "TestIssuer"
}
}
十二. 添加中间件
最后,我们需要使用 Use<>
扩展方法注册自定义中间件。
using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
app.UseMiddleware<JwtMiddleware>();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
十三. 创建身份验证请求和响应类
创建 AuthenticateResponse
类用于令牌值设置和获取。
namespace Custom_Jwt_Token_Example.Models
{
public class AuthenticateResponse
{
public string Token { get; set; }
}
}
创建 AuthenticateRequest
类,用于发送用户名和密码的身份验证请求。
using System.ComponentModel.DataAnnotations;
namespace Custom_Jwt_Token_Example.Models
{
public class AuthenticateRequest
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
}
十四. 创建角色枚举和用户类
创建一个角色枚举,用于存储管理员和客户用户。
namespace Custom_Jwt_Token_Example.Models
{
public enum Role
{
Admin,
Customer
}
}
创建一个用户类,用于存储用户信息。
using System.Text.Json.Serialization;
namespace Custom_Jwt_Token_Example.Models
{
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public List<Role> Role { get; set; }
[JsonIgnore]
public string Password { get; set; }
}
}
十五. 创建 AppSettings 类
创建一个 AppSettings
类,用于存储密钥和发行者值。
namespace Custom_Jwt_Token_Example.Helper
{
public class AppSettings
{
public string Key { get; set; }
public string Issuer { get; set; }
}
}
十六. 示例的运行效果说明
-
用户登录:用户通过 /api/authentication/login
端点发送用户名和密码进行身份验证,成功后返回 JWT 令牌。 -
访问受保护的资源:用户在访问受保护的 API(如 /weatherforecast
)时,需要在请求头中包含 Bearer 令牌,成功后返回天气预报数据。 -
角色授权:根据用户角色(如 Admin 或 Customer)限制用户访问某些 API 端点,未授权的用户会收到 401 状态码的响应。
通过以上步骤,您将能够在 ASP.NET Core Web API 中成功实现 JWT 身份验证和基于角色的授权,从而提升应用程序的安全性和灵活性。