ASP.NET Core 中的 JWT 鉴权实现
在当今的软件开发中,安全性和用户认证是至关重要的方面。JSON Web Token(JWT)作为一种流行的身份验证机制,因其简洁性和无状态特性而被广泛应用于各种应用中,尤其是在 ASP.NET Core 项目里。本文将详细介绍如何在 ASP.NET Core 应用中实现 JWT 鉴权,确保应用能够安全地验证用户身份并授权访问特定资源。
一、安装必要的 NuGet 包
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
这个包提供了 JWT 身份验证所需的所有功能,包括对 JWT 令牌的解析和验证。
二、配置 JWT 身份验证
在应用的配置文件 appsettings.json
中添加 JWT 相关的配置信息。这些配置包括密钥、发行者、受众和令牌的有效期等:
{
"JwtSettings": {
"Secret": "YourSecretKeyYourSecretKeyYourSecretKeyYourSecretKey", // 用于加密的密钥(应非常复杂)
"Issuer": "YourAppName", // 发行者
"Audience": "YourAppUsers", // 受众
"ExpireMinutes": 120 // 令牌有效期
}
}
在 Program.cs
文件中,需要配置 JWT 认证方案,以便 ASP.NET Core 知道如何处理 JWT 令牌:
var builder = WebApplication.CreateBuilder(args);
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Secret"])),
ClockSkew = TimeSpan.Zero // 默认的 5 分钟偏移时间
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Headers["Authorization"].ToString()?.Replace("Bearer ", "");
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
// 如果过期,把过期信息添加到头部
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Append("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
配置了 JWT 身份验证的中间件,确保所有传入的请求都会被检查是否包含有效的 JWT 令牌。
JwtBearerEvents的订阅事件
// 1. **OnMessageReceived**:
// -触发时机:当接收到一个身份验证请求。
// -用途:用来处理接收到的原始身份验证消息,可以根据请求的具体情况来修改或取消身份验证过程。
//2. * *OnTokenValidated * *:
// -触发时机:在JWT被成功验证后触发。
// -用途:用来处理已验证的token,例如,可以在这里添加额外的日志记录或执行一些安全检查。
//3. * *OnAuthenticationFailed * *:
// -触发时机:当身份验证失败时触发。
// -用途:用来处理身份验证失败的情况,例如,记录失败原因、执行额外的错误处理逻辑等。
//4. * *OnChallenge * *:
// -触发时机:当需要向客户端发出一个挑战(例如,要求客户端提供凭据)时触发。
// -用途:自定义挑战的响应,例如,修改返回给客户端的`401 Unauthorized`响应。
//5. * *OnForbidden * *:
// -触发时机:当授权失败时触发(即用户已通过身份验证,但没有足够的权限访问特定资源)。
// -用途:自定义处理禁止访问的情况,例如,返回一个自定义的错误消息或执行其他逻辑。
三、生成 JWT Token
为了使用户能够登录并获取 JWT 令牌,需要创建一个控制器或服务来处理登录请求并生成 JWT 令牌。以下是一个简单的登录 API 示例:
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
[ApiController]
[Route("api/[controller]")]
publicclassAuthController : ControllerBase
{
privatereadonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{
// 假设已经验证了用户名和密码
if (loginRequest.Username == "admin" && loginRequest.Password == "password")
{
var token = GenerateJwtToken(loginRequest.Username);
return Ok(new { token });
}
return Unauthorized();
}
private string GenerateJwtToken(string username)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
//添加更多的标识
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Secret"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: jwtSettings["Issuer"],
audience: jwtSettings["Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(double.Parse(jwtSettings["ExpireMinutes"])),
signingCredentials: creds
);
returnnew JwtSecurityTokenHandler().WriteToken(token);
}
}
publicclassLoginRequest
{
publicstring Username { get; set; }
publicstring Password { get; set; }
}
四、保护 API 路由
一旦有了 JWT 令牌生成机制,接下来需要保护 API 路由,确保只有携带有效 JWT 令牌的请求可以访问受保护的资源。
可以通过在控制器或操作方法上使用 [Authorize]
特性来实现:
[AllowAnonymous]
可跳过授权
[ApiController]
[Route("[controller]")]
[Authorize]
publicclassWeatherForecastController : ControllerBase
{
[Authorize]
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
五、客户端请求
客户端在请求受保护的 API 时,必须在请求头中添加 Authorization
字段,格式为 Bearer <token>
:
GET /api/protected/data HTTP/1.1
Host: yourdomain.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
这样,服务器就能通过 JWT 中间件验证令牌的有效性,并允许或拒绝请求。
六、扩展:Swagger集成,传递验证
需要使用包Swashbuckle.AspNetCore
builder.Services.AddSwaggerGen(options => options.SwaggerTokenConfigure());
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
SwaggerConfiguration将token保存到swagger并传递到后台
public staticclassSwaggerConfiguration
{
public static void SwaggerTokenConfigure(this SwaggerGenOptions options)
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "JWT Auth API",
Version = "v1",
Description = "A sample API for JWT Authentication"
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
}
}
有关swagger更多的详细配置,请参考
https://mp.weixin.qq.com/s/Xke2EyUHuxR_RdHbSXP5Ew
分组:https://mp.weixin.qq.com/s/Ut9leANrq4pJMgOQFmVkqg
七、扩展:获取身份验证信息
老规矩,先注册
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddTransient<IAppUser, AppUser>();
存放用户信息的接口和实现类
public interfaceIAppUser
{
string Username { get; }
string HeaderToken { get; }
}
publicclassAppUser : IAppUser
{
privatereadonly HttpContext _httpContext;
public AppUser(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor?.HttpContext;
}
publicstring Username { get => _httpContext?.User.Claims.FirstOrDefault(s => s.Type == "Username")?.Value.ToString()??""; }
publicstring HeaderToken
{
get
{
if (_httpContext.Request.Headers.ContainsKey("Authorization"))
{
return _httpContext?.Request.Headers["Authorization"].ToString()
.Replace("Bearer ", "").Trim();
}
returnstring.Empty;
}
}
}
直接使用
private readonly IAppUser _appUser ;
public AuthController(IConfiguration configuration, IAppUser appUser)
{
_configuration = configuration;
_appUser=appUser;
}
[HttpGet]
public IActionResult Getuserinfo()
{
return Ok(new { _appUser.Username, _appUser.HeaderToken });
}
可以成功获取到用户存放的token信息,不过现在还有一个弊端,就是每次都需要构造注入才可以过去用户信息,是比较麻烦的,其实我们可以通过封装一下,将AppUser存放到App全局配置里面直接静态获取,这样在需要获取用户信息的时候,就不需要每次都构造注入了
等后面有机会出优化和封装教程,或者直接参考:https://gitee.com/Pridejoy/MalusAdmin
八、可选:角色授权
如果应用需要基于角色进行授权,可以在生成 JWT 令牌时添加角色信息:
private string GenerateJwtToken(string username)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(ClaimTypes.Role, "Admin"), // 添加角色
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
// 剩余代码与前面一致
}
然后,可以使用 [Authorize(Roles = "Admin")]
特性来限定只有特定角色的用户才能访问:
[Authorize(Roles = "Admin")]
[HttpGet("admin-data")]
public IActionResult GetAdminData()
{
return Ok(new { message = "This is admin data" });
}
总结
通过以上操作,就可以在 ASP.NET Core 应用中实现 JWT 鉴权,确保你的应用能够安全地验证用户身份并授权访问特定资源。JWT 的无状态特性和灵活性使其成为现代 Web 应用中身份验证的理想选择。