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日

相关文章

  • .Net Core 多文件打包压缩的实现代码

    .NET Core 多文件打包压缩的实现代码 在.NET Core应用程序中,有时需要将多个文件打包成一个压缩文件,以便于传输或存储。在本攻略中,我们将介绍如何使用C#代码实现多文件打包压缩,并提供两个示例说明。 1. 使用System.IO.Compression命名空间 在.NET Core应用程序中,可以使用System.IO.Compression命…

    C# 2023年5月16日
    00
  • Unity实现汽车前后轮倒车轨迹计算

    Unity实现汽车前后轮倒车轨迹计算攻略 在制作汽车驾驶、停车等游戏时,经常需要计算汽车倒车轨迹。本文介绍如何使用Unity实现汽车倒车轨迹计算的完整攻略。 步骤一:创建汽车模型 首先,需要创建一辆汽车模型,包括车身、车轮等组成部分。可以使用Unity自带的模型,也可以自行创建或引入其他模型。 步骤二:设置车轮转动 将车轮设置为可以旋转,可以通过Animat…

    C# 2023年6月3日
    00
  • C#如何Task执行任务,等待任务完成

    当我们需要在应用程序中执行耗时的任务时,我们可以使用Task类。下面是使用Task执行任务并等待任务完成的攻略: 创建Task任务 要创建一个Task,我们可以使用Task.Run()方法来启动一个任务。该方法接受一个委托(Delegate)类型的参数,该委托指定要在新线程上运行的代码。 例如,下面是一个简单的Task创建示例: Task task = Ta…

    C# 2023年6月6日
    00
  • Jquery插件仿百度搜索关键字自动匹配功能

    Jquery插件仿百度搜索关键字自动匹配功能是一种常见的前端开发技术,可以提高用户体验。以下是使用Jquery插件实现仿百度搜索关键字自动匹配功能的完整攻略。 环境准备 在使用Jquery插件前,需要引入Jquery库和Jquery插件。可以使用以下命令来引入Jquery库和Jquery插件: <script src="https://cod…

    C# 2023年5月15日
    00
  • C#异步编程Task的创建方式

    C#中的异步编程是为了方便对于耗时操作的处理,而Task是一种比较常用的异步编程工具,在这里,我将为您提供完整的C#异步编程Task的创建方式攻略。 示例一:使用Task.Run()方法创建一个异步任务 在C#中,可以使用Task.Run()方法创建一个异步任务,在这个异步任务中,我们可以执行需要异步处理的操作。 async Task Method1() {…

    C# 2023年6月6日
    00
  • C#Process的OutputDataReceived事件不触发问题及解决

    首先需要说明的是,C#中的Process类可以用于启动和管理外部进程,包括可以获取该进程的标准输出流等信息。然而,有时候我们会遇到Process类中OutputDataReceived事件不触发的问题,也就是说并不能获取到进程的标准输出流信息。 出现这个问题的原因有多种,比如: 进程的输出缓冲区被填满; 进程输出数据流的标准输出缓冲区不存在; 异步读取操作运…

    C# 2023年6月6日
    00
  • ASP.NET MVC API 接口验证的示例代码

    下面是关于“ASP.NET MVC API 接口验证的示例代码”的完整攻略: 一、背景介绍 ASP.NET MVC是一种基于MVC(Model-View-Controller,模型-视图-控制器)的开发模式来创造Web应用程序的思想。ASP.NET Core是一个跨平台的、高性能的框架,可以用于构建Web应用程序、RESTful API、微服务,等等。 二、…

    C# 2023年5月31日
    00
  • mssql 存储过程调用C#编写的DLL文件

    下面将为你详细讲解“mssql 存储过程调用C#编写的DLL文件”的完整攻略。 什么是存储过程? 首先,需要明确存储过程的概念。存储过程是一组T-SQL语句的预编译,它们一同形成一个可重复使用的功能模块。存储过程在实际应用中具有很大的优势,包括提高性能、确保安全性等。 如何调用C#编写的DLL文件? C#是一种通用的、面向对象的编程语言。C#编写的DLL文件…

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