文章目录
- 需求
- 准备
- 创建JWT配置
- 创建JWTService
- 注册JWT
- 创建中间件读取jwt的token
- 在需要的接口上添加属性
- 启动认证
- 启动swagger的授权认证
- 使用
需求
实现一个记录某个用户所有操作的功能
准备
- 创建你的webapi项目
- 从nuget下载安装JWT资源包根据你的项目使用.net版本下载对应的jwt版本,测试项目使用了.net8.0:
Microsoft.AspNetCore.Authentication.JwtBearer
创建JWT配置
在appsettings.json中新增JWTOptions
"JWTOptions": {//你的jwt加密密钥"SecretKey": "ThisIsASecretKeyForJWTTokenGeneration","Issuer": "localhost", //令牌颁发者"Audience": "localhost", //令牌接收者"Expired": 5 //令牌过期时间
}
创建jwt配置类并注册
public class JWTOptions
{/// <summary>/// jwt加密密钥,任意字符串/// </summary>public string SecretKey { get; set; }/// <summary>/// 颁发者/// </summary>public string Issuer { get; set; }/// <summary>/// 接收者/// </summary>public string Audience { get; set; }/// <summary>/// 过期时间/// </summary>public int Expired { get; set; }
}//在program.cs中注册该option
builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWTOptions"));
创建JWTService
创建服务生成token
public class GenerateJWTService
{private readonly JWTOptions _options;public GenerateJWTService(IOptionsMonitor<JWTOptions> options){_options = options.CurrentValue;}public JWTokenTResult GenerateToken(string userName){var claims = new List<Claim> {new("userName", userName),new(JwtRegisteredClaimNames.Sub, userName),};var validFrom = DateTime.Now;var validTo = DateTime.Now.AddMinutes(10);//创建令牌var jwt = new JwtSecurityToken(issuer: _options.Issuer,audience: _options.Audience,claims: claims,notBefore: validFrom,expires: validTo,signingCredentials: new Microsoft.IdentityModel.Tokens.SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey)), SecurityAlgorithms.HmacSha256)) ;string accessToken = new JwtSecurityTokenHandler().WriteToken(jwt);return new JWTokenTResult{AccessToken = accessToken,ExpireIn = _options.Expired * 60,TokenType = JwtBearerDefaults.AuthenticationScheme};}
}//jwt模型
public class JWTokenTResult
{public string AccessToken { get; set; }public string RefreshToken { get; set; }/// <summary>/// 过期时间,单位s/// </summary>public int ExpireIn { get; set; }public string TokenType { get; set; }public LoginUserModel User { get; set; }
}public class LoginUserModel
{public string UserId { get; set; }public string UserName { get; set; }public string Roles { get; set; }
}
注册JWT
把jwt注册到服务中
public static void AddJWTTokenAuth(this IServiceCollection serivces, IConfiguration configuration)
{var jwtSettings = configuration.GetSection("JWTOptions");serivces.Configure<JWTOptions>(configuration.GetSection("JWTOptions"));serivces.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.SaveToken = true;options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = true,ValidateAudience = true,ValidateLifetime = true,//启动token有效时间校验ClockSkew = TimeSpan.Zero, //默认ClockSkew是5分钟,当前时间和JWT的过期时间之间的差距小于 5 分钟,Token 仍然会被认为是有效的ValidateIssuerSigningKey = true,ValidIssuer = jwtSettings["Issuer"],ValidAudience = jwtSettings["Audience"],IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]))};});
}//在program.cs中调用
builder.Services.AddJWTTokenAuth(builder.Configuration);
创建中间件读取jwt的token
using System.IdentityModel.Tokens.Jwt;
using System.Text;namespace JWTTest
{public class UserMiddleware{private readonly RequestDelegate _next;private readonly GenerateJWTService _generateJWTService;public UserMiddleware(RequestDelegate next, GenerateJWTService generateJWTService){_next = next;_generateJWTService = generateJWTService;}public async Task InvokeAsync(HttpContext context){if(context.User.Identity.IsAuthenticated){var requestUrl = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";if(context.Request.Method.ToUpper() == "GET"){}else{context.Request.EnableBuffering();// 启用请求体流缓冲,以便多次读取var reader = new StreamReader(context.Request.Body, Encoding.UTF8);var body = await reader.ReadToEndAsync();// 将请求体位置重置,避免后续中间件或控制器读取不到context.Request.Body.Position = 0;}var userName = context.User.Claims.FirstOrDefault(c => c.Type == "userName")?.Value;Console.WriteLine($"{userName} request");var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();var tokenhandler = new JwtSecurityTokenHandler();var jwtToken = tokenhandler.ReadToken(token) as JwtSecurityToken;if(jwtToken != null){//如果token将要过期,实现用户无感刷新,只需要前端判断response的header中是否有New-Access-Token,有就替换原来的tokenif(jwtToken.ValidTo - DateTime.UtcNow < TimeSpan.FromMinutes(5)){var newAccessToken = _generateJWTService.GenerateToken(userName);context.Response.Headers["New-Access-Token"] = "";//newAccessToken;}}}await _next(context);}}
}
在需要的接口上添加属性
在需要的接口上添加[Authorize]属性,也可以加到controller上,然后给不需要认证的接口添加[AllowAnonymous],跳过认证
using JWTTest.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;namespace JWTTest.Controllers
{[ApiController][Route("[controller]/[action]")]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;private readonly GenerateJWTService _generateJWTService;public WeatherForecastController(ILogger<WeatherForecastController> logger, GenerateJWTService generateJWTService){_logger = logger;_generateJWTService = generateJWTService;}[HttpGet(Name = "GetWeatherForecast")][Authorize]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();}[Authorize][HttpPost("test/testpost")]public ActionResult Test(LoginUserModel loginUserModel){return default;}[HttpGet("/login")]public ActionResult GetLogin(string name, string password){var jwtTokenResult = _generateJWTService.GenerateToken(name);//jwtTokenResult.refresh_token = refreshToken;return Ok(jwtTokenResult);//这里可按需返回 如果不想返回用户信息 比如密码 可以在_generateJwt.GenerateEncodedTokenAsync去掉哦}}
}
启动认证
//在program.cs中启动认证,顺序不能错
app.UseAuthentication(); //身份验证,验证令牌信息
app.UseAuthorization();//授权
启动swagger的授权认证
using Microsoft.OpenApi.Models;namespace Wonder.OHTC.Backend.Extension
{public static class SwaggerAuthExtension{/// <summary>/// 为swagger添加authorization/// </summary>/// <param name="services"></param>public static void AddSwaggerExtension(this IServiceCollection services){services.AddSwaggerGen(options =>{// 为 Swagger JSON and UI设置xml文档注释路径var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)var xmlPath = Path.Combine(basePath, "Wonder.OHTC.Backend.xml");// 添加控制器层注释,true表示显示控制器注释 false表示只显示API接口的注释options.IncludeXmlComments(xmlPath, true);options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme(){In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)Description = "Please enter JWT with bearer into field",Name = "Authorization",//jwt默认的参数名称Type = SecuritySchemeType.ApiKey});options.AddSecurityRequirement(new OpenApiSecurityRequirement(){{new OpenApiSecurityScheme{Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,Id = "Bearer"}},Array.Empty<string>()}});});}}
}//在program.cs中调用
builder.Services.AddSwaggerExtension();
也可以直接在program里面直接注册jwt
using JWTTest.Model;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Text;namespace JWTTest
{public class Program{public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbucklebuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen(options =>{options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme(){In = Microsoft.OpenApi.Models.ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)Description = "Please enter JWT with bearer into field",Name = "Authorization",//jwt默认的参数名称Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey});options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement(){{new OpenApiSecurityScheme{Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,Id = "Bearer"}},new string[]{}}});});var jwtSettings = builder.Configuration.GetSection("JWTOptions");builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.SaveToken = true;options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = true,ValidateAudience = true,ValidateLifetime = true,//启动token有效时间校验ClockSkew = TimeSpan.Zero, //默认ClockSkew是5分钟,当前时间和JWT的过期时间之间的差距小于 5 分钟,Token 仍然会被认为是有效的ValidateIssuerSigningKey = true,ValidIssuer = jwtSettings["Issuer"],ValidAudience = jwtSettings["Audience"],IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]))};});builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWTOptions"));builder.Services.AddSingleton<GenerateJWTService>();var app = builder.Build();// Configure the HTTP request pipeline.if (app.Environment.IsDevelopment()){app.UseSwagger();app.UseSwaggerUI();}app.UseHttpsRedirection();app.UseAuthentication(); //身份验证app.UseAuthorization();//授权app.MapControllers();app.UseMiddleware<UserMiddleware>();app.Run();}}
}