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#8.0默认接口实现的详细实例

    下面是关于C#8.0默认接口实现的详细攻略: 什么是C#8.0的默认接口实现? 默认接口实现是C#8.0中引入的新功能,它允许我们为接口的成员提供默认的实现,这样所有实现该接口的类都可以直接继承这些默认实现,而不需要再次手动实现这些接口成员。 如何使用C#8.0的默认接口实现? 我们使用关键字 default 来定义接口的默认成员实现。 例如,假设我们有一个…

    C# 2023年6月7日
    00
  • C#使用CefSharp控件实现爬虫

    以下是详细讲解“C#使用CefSharp控件实现爬虫”的完整攻略: 1.什么是CefSharp CefSharp是一种基于Chromium Embedded Framework(CEF)的.NET开源项目,通过将Chromium增加到应用程序中,可以在WinForms和WPF应用程序中嵌入HTML内容,运行JavaScript等。它极大地提高了.NET应用程…

    C# 2023年6月7日
    00
  • C#中数组、ArrayList、List、Dictionary的用法与区别浅析(存取数据)

    下面是关于C#中数组、ArrayList、List、Dictionary的用法与区别浅析(存取数据)的完整攻略。 数组 定义 数组是一种数据结构,可以在单个变量下存储多个值。在C#中,数组是由相同类型的元素组成的集合。可以使用数组来存储一个固定数量的元素,这些元素在创建数组时就已被确定。 用法 创建数组 在C#中创建数组,需要指定数组的长度,然后使用关键字n…

    C# 2023年5月31日
    00
  • c# 网络编程之tcp

    C# 网络编程之TCP TCP是传输控制协议,是一种无连接的、可靠的、基于字节流的传输协议,它能够在网络上确保数据的可靠传输。在C#/.NET中,我们可以使用System.Net.Sockets命名空间下的TcpClient和TcpListener类来实现TCP网络编程。 TCP客户端 连接服务器 要建立一个TCP连接,需要指定服务器的IP地址和端口号,并使…

    C# 2023年5月31日
    00
  • 改进c# 代码的五个技巧(一)

    当我们编写C#代码时,既希望代码功能完善,也希望代码运行速度和内存占用量方面尽可能优化。在这篇文章中,我们会介绍五个技巧,可以帮助你改进C#代码的质量。 技巧一:使用StringBuilder代替String 使用String类型声明的变量在处理文本时会创建一个新的字符串对象,如果需要在原始字符串上添加字符,则需要使用连接符+。这样使用+连接字符串会导致系统…

    C# 2023年5月15日
    00
  • C#动态对象(dynamic)详解(实现方法和属性的动态)

    C#动态对象(dynamic)详解 — 实现方法和属性的动态 在C#中,dynamic类型是一种非常方便的类型,它可以允许我们在运行时动态地创建和操作对象,这是非常有用的。在这篇文章中,我们将简要介绍C#动态对象(dynamic)的概念,并演示如何实现方法和属性的动态。 什么是C#动态对象(dynamic) C#动态对象(dynamic)是C#语言中的一种…

    C# 2023年6月1日
    00
  • c#使用windows服务更新站点地图的详细示例

    下面是“c#使用windows服务更新站点地图的详细示例”的完整攻略,本文将由以下几部分组成:需求分析、技术选型、开发流程和实现示例。 需求分析 我们需求是实现一个使用 Windows 服务来自动更新网站地图(SiteMap)的功能。这个服务需要能够自动遍历网站,根据业务逻辑生成站点地图,并更新网站。在此基础上,我们可以选择以特定的时间间隔来调度这个服务。 …

    C# 2023年5月31日
    00
  • python代码中怎么换行

    Python代码中换行有以下几种方式: 方法一:使用“\” 在Python中,我们可以使用“\”字符来将长代码拆分成多行。在“\”字符后面加上回车符,Python会认为下一行代码是当前行的延续,直到整个表达式结束。例如: a = 1 + 2 + 3 + \ 4 + 5 + 6 + \ 7 + 8 + 9 print(a) # 输出 45 以上代码将长的表达式…

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