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

yizhihongxing

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#窗体实现点餐系统

    C#窗体实现点餐系统,是一种常见的应用场景,本文将从以下几个方面详细讲解该系统的实现过程。 系统结构设计 整个系统可以分为前台展示、后台数据管理、数据库存储三部分。其中,前台展示负责向用户展示菜单和订单信息,后台数据管理负责将用户的订单信息提交至数据库中进行管理,数据库存储则负责数据的持久化存储。 前台界面设计 本系统的前台界面主要包含以下内容: 菜单展示:…

    C# 2023年6月1日
    00
  • C# 使用相同权限调用 cmd 传入命令的方法

    为了在C#中以相同权限调用cmd传入命令,以下是步骤: 创建一个ProcessStartInfo对象来设置启动进程时使用的属性,包括ProcessStartInfo对象的文件名和WorkingDirectory属性。WorkingDirectory属性是命令执行的起始目录。 通过Process类,创建一个转到cmd.exe的进程。 在cmd.exe进程中,输…

    C# 2023年6月6日
    00
  • C# .NET 中的缓存实现详情

    C#.NET中的缓存实现详情 什么是缓存? 缓存是计算机中常用的性能优化机制之一,它将一些已经经过计算的数据暂存在计算机的内存中,以便后续的使用,以减少后续访问时的计算代价。 C#.NET中的缓存实现 在C#.NET中,可以使用System.Runtime.Caching命名空间中的MemoryCache类进行缓存的实现。 MemoryCache类的基本用法…

    C# 2023年5月15日
    00
  • C#中WebBroeser控件用法实例教程

    C#中WebBrowser控件用法实例教程 简介 WebBrowser控件可用于在C# Windows窗体应用程序中加载网页或HTML文档。其使用方法也非常简单,本文将提供WebBrowser控件的用法实例教程。 步骤 1. 在Windows Form中添加WebBrowser控件 在Visual Studio中创建一个Windows窗体应用程序,并在窗体设…

    C# 2023年6月7日
    00
  • 基于C#实现手机号码归属地接口调用

    基于C#实现手机号码归属地接口调用的完整攻略 手机号码归属地接口是一种常见的API接口,可以通过该接口查询手机号码的归属地信息。本文将提供一个基于C#实现手机号码归属地接口调用的完整攻略,包括两个示例。 步骤1:获取API接口 要使用手机号码归属地接口,首先需要获取API接口。可以在多个网站上找到提供手机号码归属地API接口的服务商。以下是一个示例: str…

    C# 2023年5月15日
    00
  • C#数组学习相关资料整理

    C# 数组学习相关资料整理 数组的定义 数组是一种能够容纳多个值的数据结构,在 C# 中也不例外。变量能够存储一个值,但有些时候我们需要存储一组相似类型的值,这种情况下我们就需要使用数组了。 声明数组 在 C# 中声明数组的方式非常简单,如下所示: // 声明一个整型数组 int[] myArray; // 声明一个字符串数组 string[] myStri…

    C# 2023年5月31日
    00
  • ASP.NET/C#中如何调用动态链接库DLL

    调用动态链接库(DLL)是在编程过程中常见的需求,本文将介绍如何在ASP.NET/C#中调用DLL文件。具体步骤如下: 第一步:在项目中添加DLL文件 将需要调用的DLL文件添加到项目中,通常可以通过以下两种方式实现: 在Visual Studio解决方案中添加现有项:右键单击要添加文件的文件夹,选择“添加现有项”,在文件对话框中选择DLL文件,单击“添加”…

    C# 2023年5月31日
    00
  • C# WinForm快捷键设置技巧

    C# WinForm快捷键设置技巧 在C# WinForm程序的开发中,设置快捷键是提高用户体验的一种重要手段。本文将详细介绍如何在WinForm中设置快捷键,包括以下内容: 设置按钮控件的快捷键 设置菜单项的快捷键 设置按钮控件的快捷键 我们可以使用Button控件的UseVisualStyleBackColor属性设置快捷键。在Button控件中设置了&…

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