当前位置: 首页 > article >正文

.Net Core webapi 实现JWT认证

文章目录

    • 需求
    • 准备
    • 创建JWT配置
    • 创建JWTService
    • 注册JWT
    • 创建中间件读取jwt的token
    • 在需要的接口上添加属性
    • 启动认证
    • 启动swagger的授权认证
    • 使用

需求

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

准备

  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();
        }
    }
}

使用

在这里插入图片描述


http://www.kler.cn/a/507733.html

相关文章:

  • Linux网络 TCP socket
  • ThreeJs能力演示——图层导入导出
  • YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练
  • 【混合开发】CefSharp+Vue 解决Cookie问题
  • 麦田物语学习笔记:代码链接UI实现时间日期对应转换
  • 如何使用PHP构建IoC容器,实现依赖注入!
  • 知识图谱综述论文阅读(一)
  • AI大模型架构背后的数学原理和数学公式,基于Transformer架构的数学公式有哪些?
  • 寒假刷题Day8
  • 【影刀_常规任务计划_API调用】
  • 深度学习-87-大模型训练之预训练和微调所用的数据样式
  • 基于PHP的校园新闻发布管理
  • Go入门学习笔记
  • SQL ON与WHERE区别
  • 架构设计:微服务还是集群更适合?
  • Java负载均衡
  • C++ 强化记忆
  • 【Linux系统】分区挂载
  • 图像的旋转之c++实现(qt + 不调包)_c++图像旋转
  • 晨辉面试抽签和评分管理系统之十:如何搭建自己的数据库服务器,使用本软件的网络版
  • 【机器学习实战入门】有趣的Python项目:使用OpenCV进行性别和年龄检测
  • [Mac + Icarus Verilog + gtkwave] Mac运行Verilog及查看波形图
  • 计算机网络 (47)应用进程跨越网络的通信
  • cpu架构
  • Linux之文件系统前世今生(二)
  • Notepad++移除所有空格