C#实现JWT无状态验证的实战应用解析

C#实现JWT无状态验证的实战应用解析

本文将讲解如何使用C#实现JWT无状态验证的实战应用。

什么是JWT?

JWT (Json Web Token)是一种用于身份验证的开放标准(RFC 7519)。它是一种轻量级的身份验证协议,通过在服务端签署一个 JSON 数据块生成一个令牌(Token),以表明身份和认证的有效性。该令牌包含了用户身份、令牌过期时间等信息。在服务端的每个请求中,该Token被使用以验证用户的身份和操作的有效性。JWT作为一种用于Web应用的跨域认证及数据传递的极佳解决方案已被广泛应用。

JWT原理

JWT由三部分组成,分别是:Header、Payload和Signature。其中,Header和Payload都是JSON对象,Signature是将Header和Payload使用相同的加盐算法和密钥生成的Hash字符串。

Header示例:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Signature示例:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),

  secret)

其中,Base64Url编码遵循RFC 4648标准,其实现方式与Base64编码略有不同。

JWT优缺点

优点

  • 无状态
  • 前后端分离
  • 扩展性好

缺点

  • 无法立即废除Token
  • 不支持单点登录

JWT的使用

生成Token

对于ASP.NET Core项目,可以使用Microsoft.AspNetCore.Authentication.JwtBearer Nuget包,快速实现生成Token的功能。

以ASP.NET Core WebAPI项目为例,首先需要添加以下依赖:

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.13" />
  <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.11.1" />
</ItemGroup>

接着,在Startup.cs中的ConfigureServices方法中,需要添加JWT验证的配置:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

...

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });

在上述代码中,我们配置了JWT的发行者(Issuer)和接受者(Audience),以及用于签署Token的密钥(Key)。

接着,在Configure方法中,需要启用JWT验证:

app.UseAuthentication();

此时,我们就可以使用System.IdentityModel.Tokens.Jwt Nuget包提供的JwtSecurityTokenHandler类生成Token:

using System.IdentityModel.Tokens.Jwt;

...

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("mysupersecret_secretkey!123");
var tokenDescriptor = new SecurityTokenDescriptor
{
    Issuer = "localhost",
    Audience = "localhost",
    Subject = new ClaimsIdentity(new[]
    {
        new Claim("id", "123456"),
        new Claim("username", "john.doe")
    }),
    Expires = DateTime.UtcNow.AddHours(1),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);

在上述代码中,我们指定了Token的发行者(Issuer)、接受者(Audience)、过期时间(Expires)、用于签署Token的密钥(key)以及Payload信息(Subject)。

验证Token

在客户端向服务端发送请求时,需要在请求头中附带Token:

GET /api/values HTTP/1.1
Host: localhost:5000
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

服务端验证Token的方法与生成Token类似。在ConfigureServices方法中,我们可以使用AddJwtBearer方法添加JWT验证的配置。

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });

Configure方法中,我们启用JWT验证:

app.UseAuthentication();

此时,我们就可以在Controller的Action中使用Authorize属性来保护需要认证的API:

[Authorize]
[HttpGet]
public IActionResult Get()
{
    var currentUser = HttpContext.User;
    var userId = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
    var username = currentUser.FindFirst(ClaimTypes.Name).Value;

    return Ok(new { UserId = userId, Username = username });
}

在上述代码中,我们使用Authorize属性来指定只有经过验证的用户才能访问该API,在Controller中可以使用HttpContext.User获取当前用户的信息。

示例应用

本节将介绍一个实现JWT身份验证的示例应用。该应用为一个简单的ASP.NET Core WebAPI项目,在前端使用Vue.js进行展示。

在该应用中,我们将完成以下功能的实现:

  • 登录、注册、登出
  • 权限管理
  • 鉴权

数据库

在实现示例应用前,我们需要先通过Entity Framework Core创建数据库。根据以上需求,我们需要创建的表包括UsersRolesUserRoles

在Package Manager Console中输入以下命令,创建数据模型:

Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir DataModels -f

在上述命令中,MyDatabase是我们要创建的数据库名称,可以根据需求进行修改。

执行上述命令后,我们在项目中会看到生成了DataModels文件夹,其中包含了数据库对应的实体类。

用户认证

在实例应用中,我们将使用JWT实现用户认证。对于需要认证的API,我们将使用Authorize属性来标记,以保护对该API的访问。

在项目中,我们需要实现以下内容:

  • 客户端向服务端发送登录请求
  • 服务端验证用户的登录信息,并返回Token
  • 客户端在后续的请求中携带Token
  • Token验证成功后,服务端返回JWT的Payload信息

后台API

在后台API中,我们需要添加以下依赖:

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.13" />
  <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.11.1" />
  <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.13" />
  <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.13" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.13" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.13" />
</ItemGroup>

接着,在Startup.cs中的ConfigureServices方法中,需要添加JWT验证的配置:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Jwt:Issuer"],
                    ValidAudience = Configuration["Jwt:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
                };
            });
}

在上述代码中,我们配置了JWT的发行者(Issuer)和接受者(Audience),以及用于签署Token的密钥(Key)。

接着,在Configure方法中,需要启用JWT验证:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

在用户登录时,应检验其提供的凭据,并返回Token。在本示例应用中,我们使用用户名和密码作为登录凭据,返回的Token中携带了用户姓名、用户角色等信息。Token的生成使用了System.IdentityModel.Tokens.Jwt Nuget包提供的JwtSecurityTokenHandler类。

[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
    var user = await _userManager.FindByNameAsync(request.Username);

    if (user != null && await _userManager.CheckPasswordAsync(user, request.Password))
    {
        var userRoles = await _userManager.GetRolesAsync(user);

        var authClaims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim("userId", user.Id.ToString()),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        foreach (var userRole in userRoles)
        {
            authClaims.Add(new Claim(ClaimTypes.Role, userRole));
        }

        var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            expires: DateTime.UtcNow.AddHours(1),
            claims: authClaims,
            signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256Signature)
            );

        return Ok(new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token),
            expiration = token.ValidTo
        });
    }

    return Unauthorized();
}

在用户重新登录时,其原Token将失效。因此,需要使其在重新登录时获取新Token。该功能可以通过实现Token的黑名单机制来实现,即服务端维护一个Token黑名单,使其失效。具体而言,可以在登录成功后,将旧Token加入黑名单,当客户端发起请求时,服务端检测Token是否在黑名单中存在,存在则拒绝访问。

前端实现

在前端中,我们使用Vue.js框架实现用户登录。在登录后,我们将在LocalStorage中保存Token,以便在后续请求中发送。为简化页面设计,我们使用了Element UI框架提供的组件。

methods中,我们实现了登录请求:

methods: {
    login() {
        axios.post('/api/account/login', {
            username: this.username,
            password: this.password
        }).then(res => {
            localStorage.setItem('token', res.data.token);
            this.$message.success('Login success');
            setTimeout(() => {
                window.location.href = '/';
            }, 1000);
        }).catch(err => {
            this.$message.error(err.response.data);
        });
    }
}

在每次发起请求时,我们首先检查LocalStorage中是否存在Token,如果存在则将其放入请求头中:

axios.interceptors.request.use(
    config => {
        const token = localStorage.getItem('token');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    error => {
        Promise.reject(error);
    }
);

鉴权

在本示例应用中,我们使用Authorize属性来实现鉴权。对于需要鉴权的API,我们使用角色来授权,需要访问该API的用户权限必须包含指定的角色。

在Controller中,我们使用[Authorize(Roles = "admin")]属性进行鉴权。具体而言,仅有具有admin角色的用户才能访问该API:

[Authorize(Roles = "admin")]
[HttpGet]
public IEnumerable<string> GetAdminData()
{
    return new string[] { "value1", "value2" };
}

将上述API返回的数据展示到前端,在前端中我们使用了v-if指令,检测用户的角色包含admin时才显示数据。

<div v-if="roles.indexOf('admin') != -1">
    <el-row :gutter="10">
        <el-col :span="8" v-for="item in adminData" :key="item">
            <el-card>{{ item }}</el-card>
        </el-col>
    </el-row>
</div>

示例代码

完整的示例代码可以在Github中获取。

总结

本文介绍了如何使用C#实现了JWT无状态验证的实战应用,并使用了两个示例来进一步说明JWT的使用方法和鉴权的实现。当使用JWT时,需要注意以下内容:

  • JWT适用于无状态的Web应用
  • Token失效机制需要自行实现
  • Token不能立即废除

在实现时,应注意保证密钥及其生成过程的安全性,同时验证客户端提交的信息,防止XSS攻击以及SQL注入等漏洞的出现。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#实现JWT无状态验证的实战应用解析 - Python技术站

(0)
上一篇 2023年5月15日
下一篇 2023年5月15日

相关文章

  • 用C#来解析PDF文件

    当我们要使用 C# 来解析 PDF 文件时,可以使用一些开源库,例如 iTextSharp、PDFSharp 和 Syncfusion.PDF 等。这些库可以帮助我们进行 PDF 文档的读取和编辑,并且提供了一些 API 用于实现文档的操作。 接下来,我们来具体讲解使用 iTextSharp 库和 PDFSharp 库来解析 PDF 文件的两个示例: 示例1…

    C# 2023年5月15日
    00
  • WinForm实现鼠标拖动控件跟随效果

    为了实现WinForm中的鼠标拖动控件跟随效果,我们需要使用下述步骤: 1. 获取鼠标位置 鼠标在界面上移动时,我们需要获取其当前位置。可以通过下面的代码来获取: private void panel1_MouseMove(object sender, MouseEventArgs e) { Point point = Control.MousePositi…

    C# 2023年6月1日
    00
  • c#简单读取文本的实例方法

    下面我给你详细讲解一下“c#简单读取文本的实例方法”的完整攻略。 一、需求 在开发过程中,我们经常需要读取文本文件中的数据,进行进一步的处理或者展示。而c#提供了多种读取文本文件的方法,本文将介绍两种简单的读取文本的方法。 二、File.ReadAllText()方法 1. 方法介绍 File.ReadAllText()方法是一个方便而简单的方法,它可以很容…

    C# 2023年6月1日
    00
  • C#实现如何使用短信平台自动通知用户实例

    C#实现使用短信平台自动通知用户 简介 短信通知是现在很多网站或应用程序都采用的一种通知方式,以及提供给客户服务的一种方式。本文将讲解如何使用C#实现自动向用户发送短信通知。 步骤 选择短信平台 首先需要选择一家短信平台进行合作,目前市面上主流的短信平台有阿里云短信、腾讯云短信、云之讯等,选择平台需考虑到短信发送成功率、价格等相关因素。 注册并获取短信API…

    C# 2023年6月6日
    00
  • c#实现网站监控查看是否正常示例

    下面我将为您详细讲解如何使用 C# 实现网站监控并查看是否正常的完整攻略。 步骤一:使用 HttpWebRequest 类发起请求 在 C# 中,我们可以使用 HttpWebRequest 类来发送 HTTP 请求并接收响应。以下是一段示例代码,用来发送 HTTP GET 请求并接收响应: string url = "http://www.exam…

    C# 2023年6月7日
    00
  • C#中Forms.Timer、Timers.Timer、Threading.Timer的用法分析

    下面就来详细讲解一下“C#中Forms.Timer、Timers.Timer、Threading.Timer的用法分析”的攻略。 前言 在C#编程中,我们经常需要使用定时器来执行一些计划任务,比如定时刷新UI、周期性地打印日志等。而在.NET Framework中,给我们提供了三种常用的定时器类,它们分别是:Forms.Timer、Timers.Timer和…

    C# 2023年5月15日
    00
  • 用序列化实现List 实例的深复制(推荐)

    使用序列化实现List实例的深复制可以保证复制后的实例与原实例完全独立而不会相互影响。下面是使用序列化实现List实例深复制的详细攻略: 什么是深复制 深复制是指复制对象时,每个对象都会被单独复制一份,这两份对象完全独立而相互没有影响。这与浅复制不同,浅复制只是把对象的引用复制一份,这样两个对象会共用同一个引用,从而相互影响。 使用序列化实现深复制 针对Li…

    C# 2023年5月31日
    00
  • C#实现前向最大匹、字典树(分词、检索)的示例代码

    如果要实现分词和检索功能,可以用前向最大匹配和字典树算法。在C#中实现这两个功能,可以按照以下步骤进行: 实现前向最大匹配算法 前向最大匹配算法是将待分词的文本从左到右进行扫描,每次取出最长的词作为分词结果。为了实现该算法,需要将待分词的文本和词典中的词进行转换,以便进行匹配。下面是C#中的前向最大匹配算法示例代码: public static List&l…

    C# 2023年5月31日
    00
合作推广
合作推广
分享本页
返回顶部