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 应用中身份验证的理想选择。

相关推荐
Leaf吧2 小时前
springboot 配置多数据源以及动态切换数据源
java·数据库·spring boot·后端
代码驿站5202 小时前
JavaScript语言的软件工程
开发语言·后端·golang
uccs3 小时前
使用 rust 创建多线程 http-server
后端·rust
Archy_Wang_13 小时前
ASP.NET Core中 JWT 实现无感刷新Token
后端·asp.net
m0_748230943 小时前
SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
spring boot·后端·pdf
好像是个likun3 小时前
spring Ioc 容器的简介和Bean之间的关系
java·后端·spring
计算机-秋大田4 小时前
基于微信小程序的电子点菜系统设计与实现(KLW+源码+讲解)
java·后端·微信小程序·小程序·课程设计
C++小厨神4 小时前
MATLAB语言的编程范式
开发语言·后端·golang
大码猴5 小时前
用好git的几个命令,领导都夸你干的好~
前端·后端·面试