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

相关推荐
uzong1 小时前
技术故障复盘模版
后端
GetcharZp1 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy3 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
公众号【林东笔记】获取资料3 小时前
Adobe Photoshop 2024:软件安装包分享和详细安装教程
ui·adobe·photoshop
AntBlack4 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9654 小时前
pip install 已经不再安全
后端