文章目录
- 需求
- 准备
- 创建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,有就替换原来的token
if(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/swashbuckle
builder.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();
}
}
}