.Net Core webapi 实现JWT认证

发布于:2025-01-22 ⋅ 阅读:(18) ⋅ 点赞:(0)

需求

实现一个记录某个用户所有操作的功能

准备

  1. 创建你的webapi项目
  2. 从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();
        }
    }
}

使用

在这里插入图片描述