c# .net core项目中使用JWT进行权限校验

简介

JWT(JSON Web Token)是一种开放标准(RFC 7519),它提供了一种在网络应用间安全传输信息的简洁、自包含的方式。JWT主要用于身份验证和授权机制,是一种轻量级的、可扩展的、自包含的身份验证和授权解决方案。以下是JWT的详细介绍:

JWT的组成

JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),这三部分之间使用点(.)分隔。

  1. 头部(Header)
    • 头部是一个JSON对象,通常包含两个字段:alg(算法)和typ(类型)。
    • alg字段指定了用于签名的哈希算法(如HMAC SHA256或RSA)。
    • typ字段通常设置为JWT。
    • 头部信息会被Base64编码后用于JWT的字符串表示中。
  2. 载荷(Payload)
    • 载荷也是一个JSON对象,包含声明(Claims)。声明是关于实体(如用户)和其他数据的声明。
    • JWT标准中定义了七个预定义的声明字段,如iss(发行人)、exp(到期时间)、sub(主题)、aud(用户)、nbf(在此之前不可用)、iat(发布时间)和jti(JWT ID)。
    • 除了这些预定义字段外,还可以添加自定义的声明字段。
    • 载荷信息同样会被Base64编码后用于JWT的字符串表示中。
  3. 签名(Signature)
    • 签名用于验证JWT的完整性和真实性。
    • 签名是通过将头部和载荷的Base64编码字符串用指定的算法(如HMAC SHA256)和密钥进行签名得到的。
    • 签名确保了JWT在传输过程中不会被篡改,并且验证了JWT的发送者身份。

案例演示

安装Nuget包

在接下来的操作步骤中,我们需要如下的Nuget包

Install-Package System.IdentityModel.Tokens.Jwt  
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

配置密钥

在配置文件appsettings.json中加入如下配置

在JWT(JSON Web Tokens)中,Issuer(发行者)、Audience(受众)和SecurityKey(密钥)是生成和验证JWT时非常重要的三个参数。下面解释这三个值通常是什么样子的:

  1. Issuer(发行者) :
    • 这个值标识了JWT的签发者。它通常是一个唯一的字符串,用于表明是哪个实体签发了这个JWT。在实际应用中,这可以是一个服务的URL、服务名称、组织名称或任何其他能够唯一标识签发者的字符串。
    • 示例:"https://auth.example.com""ExampleService"
  2. Audience(受众) :
    • 这个值标识了JWT的接收者(或受众)。它同样是一个字符串,用于表明这个JWT是打算给哪个实体使用的。这有助于JWT的接收方验证JWT是否真的是为他们而签发的。
    • 示例:"https://api.example.com""ExampleClientApp"
    • 在某些情况下,JWT可以有多个受众,这时可以使用一个包含多个受众字符串的数组。但是,在大多数JWT库中,aud(受众)字段通常只接受单个字符串或字符串数组的一个元素。
  3. SecurityKey(密钥) :
    • 这个值用于签名JWT,以确保JWT的完整性和来源的可靠性。它应该是一个保密的字符串,只有JWT的签发者和验证者知道。这个密钥可以是任何足够复杂和难以猜测的字符串,但出于安全考虑,通常建议使用随机生成的密钥或密钥对(在使用RSA等算法时)。
    • 示例:一个随机生成的字符串,如 "8f7c09b7-03a5-4d36-843c-e82f13e56365",或者使用专门的密钥管理工具生成的密钥。
    • 注意:在生产环境中,应确保密钥的安全存储,避免硬编码在代码中或容易泄露的地方。

当生成JWT时,发行者(iss)和受众(aud)会被包含在JWT的headerpayload部分中,并使用密钥(SecurityKey)进行签名。接收JWT的实体可以通过验证签名和检查发行者和受众来确认JWT的合法性和有效性。

Token生成服务

首先我们新建一个User实体类,用于声明用户的名称和密码

cs 复制代码
public class UserRes
{
    /// <summary>
    /// 用户名
    /// </summary>
    [Required]
    public string Name { get; set; }

    /// <summary>
    /// 密码
    /// </summary>
    [Required]
    public string Password { get; set; }
}

然后新建CustomJWTService 的类,它用于生成和返回一个基于 JSON Web Tokens (JWT) 的访问令牌。

cs 复制代码
public class CustomJWTService
{
    private readonly JwtTokenOptions _jwtTokenOptions;

    public CustomJWTService(IOptionsMonitor<JwtTokenOptions> jwtTokenOptions)
    {
        _jwtTokenOptions = jwtTokenOptions.CurrentValue;
    }

    public string GetToken(UserRes user)
    {
        var claims = new List<Claim>()
        {
            new Claim("Name", user.Name),
            new Claim("Password", user.Password),
        };

        //需要加密:需要加密key:
        //Nuget引入:Microsoft.IdentityModel.Tokens
        SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtTokenOptions.SecurityKey));

        SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        //token过期时间,10分钟有效期
        var expires = DateTime.Now.AddMinutes(10);

        //Nuget引入:System.IdentityModel.Tokens.Jwt
        JwtSecurityToken token = new JwtSecurityToken(
            issuer: _jwtTokenOptions.Issuer,
            audience: _jwtTokenOptions.Audience,
            claims: claims,
            expires: expires,
            signingCredentials: creds);

        string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
        return returnToken;
    }
}

代码解读:

  • GetToken 方法接收一个 UserRes 类型的参数 user,这个参数预期包含用户的名称和密码(在实践中,通常不建议在 JWT 令牌中包含密码,因为它增加了安全风险)。
  • 创建一个 List<Claim> 来存储要包含在 JWT 中的声明(claims)。在这个例子中,声明包括用户的名称和密码。
  • 使用 SymmetricSecurityKey 和从配置中获取的密钥(_jwtTokenOptions.SecurityKey)创建一个 SymmetricSecurityKey 实例。这个密钥用于签名 JWT,以确保其完整性和真实性。
  • 创建一个 SigningCredentials 实例,它指定了用于签名的算法(HmacSha256)和密钥。
  • 设置令牌的过期时间为当前时间加上 10 分钟。
  • 使用 JwtSecurityToken 构造函数创建一个新的 JWT 令牌。这个构造函数需要发行者、受众、声明、过期时间和签名凭据作为参数。
  • 使用 JwtSecurityTokenHandlerWriteToken 方法将 JwtSecurityToken 对象序列化为一个字符串,这个字符串就是 JWT 令牌,可以发送给客户端。

依赖注入

cs 复制代码
//读取appsettings的JWTTokenOptions,注册JWT
builder.Services.Configure<JwtTokenOptions>(builder.Configuration.GetSection("JWTTokenOptions"));
//将CustomJWTService类注册为ASP.NET Core DI容器中的单例服务。
builder.Services.AddSingleton<CustomJWTService>();

控制器增加获取token接口

新建controller,测试一下我们的获取token服务

cs 复制代码
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    private CustomJWTService _jwtService;

    public TestController(CustomJWTService jwtService)
    {
        _jwtService = jwtService;
    }

    [HttpPost]
    public string GetToken(UserRes userRes)
    {
        return _jwtService.GetToken(userRes);
    }

}

启动项目,在swagger中设置user信息,发送请求!

将得到的token复制到在线jwt解密网站 jwt解密/加密 - bejson在线工具 解码一下,可以看到payload中的信息就是我们刚刚输入的user信息,证明token有效。

JWT校验中间件

得到了token,接下来我们就要使用token来进行请求的校验鉴权操作了

因为代码比较多,为了代码的整洁性,我们新建一个WebApplicationBuilder的扩展类,并将刚刚的依赖注入代码也搬过来

cs 复制代码
public static class HostBuiderExtend
{
    public static void Register(this WebApplicationBuilder builder)
    {
        //读取appsettings的JWTTokenOptions,注册JWT
        builder.Services.Configure<JwtTokenOptions>(builder.Configuration.GetSection("JWTTokenOptions"));
        //将CustomJWTService类注册为ASP.NET Core DI容器中的单例服务。
        builder.Services.AddSingleton<CustomJWTService>();
        
        #region JWT校验

        //增加鉴权逻辑
        JwtTokenOptions tokenOptions = new JwtTokenOptions();
        builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
        
        //需要引入nuget包,Microsoft.AspNetCore.Authentication.JwtBearer
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //Scheme
            .AddJwtBearer(options => //这里是配置的鉴权的逻辑
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    //JWT有一些默认的属性,就是给鉴权时就可以筛选了
                    ValidateIssuer = true, //是否验证Issuer
                    ValidateAudience = true, //是否验证Audience
                    ValidateLifetime = true, //是否验证失效时间
                    ValidateIssuerSigningKey = true, //是否验证SecurityKey
                    ValidAudience = tokenOptions.Audience, //
                    ValidIssuer = tokenOptions.Issuer, //Issuer,这两项和前面签发jwt的设置一致
                    IssuerSigningKey =
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey)) //拿到SecurityKey 
                };
            });
        #endregion
    }
}

然后在Program.cs文件中调用扩展方法,并且启动权限验证

app.UseAuthentication();

app.UseAuthentication(); 调用将认证中间件添加到应用程序的请求处理管道中。这个中间件负责处理与用户身份验证相关的所有事情,比如读取请求中的认证令牌(如JWT令牌、Cookie等),验证这些令牌的有效性,以及将验证结果(如用户身份)附加到当前请求上,以便后续中间件或控制器可以访问。

简而言之,UseAuthentication 是处理"你是谁?"这个问题的,它确保了应用程序能够识别出当前请求是由哪个用户发出的。

app.UseAuthorization();

app.UseAuthorization(); 调用将授权中间件添加到应用程序的请求处理管道中,并且这个中间件应该总是在UseAuthentication之后调用。授权中间件使用认证中间件提供的信息(比如当前用户的身份)来决定当前用户是否有权访问请求的资源。这包括检查用户是否拥有足够的权限或角色来访问特定的控制器、操作或资源。

简而言之,UseAuthorization 是处理"你能做什么?"这个问题的,它确保了只有被授权的用户才能访问特定的应用程序功能或资源。

控制器增加获取用户信息接口

接下来我们在控制器中添加一个get请求,并使用[Authorize]属性标记,在请求进来时,JWT认证中间件会验证token的有效性。然后我们将token中我们写进去的用户信息拿出来,返回给调用请求者。

cs 复制代码
    [HttpGet]
    [Authorize]
    public UserRes GetUser()
    {
        //解析token
        string token = Request.Headers["Authorization"];
        if (token.StartsWith("Bearer "))
        {
            token = token.Substring("Bearer ".Length);
        }

        var handler = new JwtSecurityTokenHandler();
        var jwtToken = handler.ReadJwtToken(token);
        var nameClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "Name").Value;
        var passwordClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "Password").Value;
        var userRes = new UserRes()
        {
            Name = nameClaim,
            Password = passwordClaim
        };
        return userRes;
    }

因为swagger中我没有开启携带tokne,因此这里使用postman进行演示,先直接对我们的接口发送一个请求,会得到一个401的响应,这是因为我们没有设置token。

状态码 401 Unauthorized 代表客户端错误,指的是由于缺乏目标资源要求的身份验证凭证,发送的请求未得到满足。

在请求头(headers)中添加一个Authorization,值为 Bearer + 空格 + 我们上一个请求获得的token,这时发送请求,权限校验可以通过了,并且刚刚设置的用户信息就被解析出来并返回了。

相关推荐
时光追逐者7 小时前
.NET 9 中 LINQ 新增功能实操
开发语言·开源·c#·.net·.netcore·linq·微软技术
宝桥南山2 天前
.NET 9 - BinaryFormatter移除
microsoft·微软·c#·asp.net·.net·.netcore
卫龙~3 天前
Django token 生成与验证
python·django·token
PasteSpider3 天前
贴代码框架PasteForm特性介绍之datetime,daterange
前端·html·.netcore·crud
csdn_aspnet3 天前
ASP.NET Core Webapi 返回数据的三种方式
.netcore
技术拾荒者5 天前
Net.Core Mvc 添加 log 日志
c#·asp.net·mvc·.netcore
kali-Myon6 天前
ctfshow-web入门-JWT(web345-web350)
前端·学习·算法·web安全·node.js·web·jwt
一包烟电脑面前做一天6 天前
.netcore + postgis 保存地图围栏数据
.netcore·postgis·geometry·polygon
yz-俞祥胜6 天前
杨中科 .Net Core 笔记 DI 依赖注入2
笔记·.netcore