.NET 实现JWT登录认证

.NET 实现JWT登录认证

在ASP.NET Core应用程序中,使用JWT进行身份验证和授权已成为一种流行的方式。JWT是一种安全的方式,用于在客户端和服务器之间传输用户信息。

添加NuGet包

首先,我们需要添加一些NuGet包来支持JWT身份验证。在您的ASP.NET Core项目中,打开Startup.cs文件,并在ConfigureServices方法中添加以下代码:

1
2
3
4
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// ...

/// <summary>
/// jwt服务
/// </summary>
/// <param name="services"></param>
protected virtual void ServicesJwtToken(IServiceCollection services)
{


var config = configuration.GetSection("App:JWtSetting").Get<JwtSettings>(); // 从appsettings.json读取JwtConfig配置
// 添加JWT身份验证服务
services.AddAuthentication(options =>
{
options.RequireAuthenticatedSignIn = true;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
//cokkie名称
options.Cookie.Name = "Z.BearerCokkie";
//cokkie过期时间
options.ExpireTimeSpan = TimeSpan.FromMinutes(config!.CokkieExpirationMinutes);
//cokkie启用滑动过期时间
options.SlidingExpiration = false;

options.LogoutPath = "/Home/Index";

})
.AddJwtBearer(options =>
{

options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true, //是否验证Issuer
ValidIssuer = config!.Issuer, //发行人Issuer
ValidateAudience = true, //是否验证Audience
ValidAudience = config.Audience,//
ValidateIssuerSigningKey = true, //是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)), //SecurityKey
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
RequireExpirationTime = true,
SaveSigninToken = true,
};

options.Events = new JwtBearerEvents
{
OnMessageReceived = async context =>
{
var token = context.Request.Cookies["access_token"]; // 从Cookie中获取token值
if (!string.IsNullOrEmpty(token))
{
context.Token = token; // 将token值设置到JwtBearer上下文中的Token属性
}
}
};

options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Cookies["x-access-token"];

if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}

return Task.CompletedTask;
},
};
});
}

这里我们使用AddAuthentication方法添加了JWT身份验证服务,并设置了默认的认证方案为JwtBearerDefaults.AuthenticationScheme,这是JWT身份验证的默认方案。

AddJwtBearer方法中,我们通过GetSection方法从appsettings.json文件中读取了一个名为JWtSetting的配置,其中包含了JWT的一些信息,例如签发者(Issuer)、接收者(Audience)、秘钥(SecretKey)等。这些信息将用于验证和生成JWT令牌。

Configure方法中添加JWT认证中间件:

1
2
3
4
5
6
7
8
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他中间件配置...

app.UseAuthentication();
app.UseAuthorization();
//...
}

配置appsettings.json

接下来,我们需要在appsettings.json文件中配置JWT的相关信息。在您的ASP.NET Core项目中,找到appsettings.json文件,并添加以下配置:

1
2
3
4
5
6
7
8
9
10
11
{
// ...
"JWtSetting": {
"Issuer": "Z.NetWiki",
"Audience": "Z.NetWiki",
"SecretKey": "zhoulucky210@163.com",
"AccessTokenExpirationMinutes": 60,
"RefreshTokenExpirationMinutes": 1440,
"CokkieExpirationMinutes": 30
}
}

您可以根据自己的需求修改配置项的值。这里的AccessTokenExpirationMinutesRefreshTokenExpirationMinutes分别表示访问令牌和刷新令牌的过期时间,单位为分钟。

创建 JWT 设置类

接下来,我们需要创建一个 C# 类来表示 JWT 的配置项,并使用 IOptions 接口将其注入到需要的地方。以下是一个示例的 JwtSettings 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class JwtSettings
{
/// <summary>
/// 发行者
/// </summary>
public string Issuer { get; set; }

/// <summary>
/// 受众
/// </summary>
public string Audience { get; set; }

/// <summary>
/// secretKey值
/// </summary>
public string SecretKey { get; set; }

/// <summary>
/// 访问令牌过期时间
/// </summary>
public int AccessTokenExpirationMinutes { get; set; }

/// <summary>
/// cokkie过期时间
/// </summary>
public int CokkieExpirationMinutes { get; set; }

/// <summary>
/// 刷新令牌过期时间
/// </summary>
public int RefreshTokenExpirationMinutes { get; set; }
}

这个类定义了与 appsettings.json 文件中的配置项相对应的属性。

用户模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserTokenModel
{
public virtual string UserId { get; set; }
public virtual string UserName { get; set;}

public virtual string[]? RoleIds { get; set;}

public virtual string[]? RoleNames { get; set;}

public virtual Claim[] Claims { get; set; }

}


实现JWT登录认证

现在,我们可以开始实现JWT登录认证的逻辑。我们将创建一个JwtService类,用于生成和验证JWT令牌。在您的ASP.NET Core项目中,创建一个名为JwtService.cs的类文件,然后添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Z.Ddd.Domain.UserSession;
using Z.Module.DependencyInjection;

namespace Z.Ddd.Domain.Authorization;

public class JwtTokenProvider : IJwtTokenProvider
{
private readonly JwtSettings _jwtConfig;
public JwtTokenProvider(IConfiguration configuration)
{
_jwtConfig = configuration.GetSection("App:JWtSetting").Get<JwtSettings>() ?? throw new ArgumentException("请先检查appsetting中JWT配置");
}
public string GenerateAccessToken(UserTokenModel user)
{
// 设置Token的Claims
List<Claim> claims = new List<Claim>
{
new Claim(ZClaimTypes.UserName, user.UserName), //HttpContext.User.Identity.Name
new Claim(ZClaimTypes.UserId, user.UserId.ToString()),
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes)).ToUnixTimeSeconds()}"),
new Claim(ZClaimTypes.Expiration, DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes).ToString()),
};

if (user.RoleIds != null && user.RoleIds.Any())
{
claims.AddRange(user.RoleIds.Select(p => new Claim(ZClaimTypes.RoleIds, p.ToString())));
}
if (user.RoleNames != null && user.RoleNames.Any())
{
claims.AddRange(user.RoleNames.Select(p => new Claim(ZClaimTypes.Role, p)));
}

user.Claims = claims.ToArray();

// 生成Token的密钥
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SecretKey));

// 生成Token的签名证书
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256);

// 设置Token的过期时间
DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes);

// 创建Token
JwtSecurityToken token = new JwtSecurityToken(
_jwtConfig.Issuer,
_jwtConfig.Audience,
claims,
expires: expires,
signingCredentials: creds
);

// 生成Token字符串
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);

return tokenString;
}

public bool ValidateAccessToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _jwtConfig.Issuer,
ValidAudience = _jwtConfig.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(_jwtConfig.SecretKey))
}, out var validatedToken);
}
catch (Exception x)
{
return false;
}
return true;
}
}

public interface IJwtTokenProvider : ITransientDependency
{
string GenerateAccessToken(UserTokenModel user);
bool ValidateAccessToken(string token);
}

这里我们创建了一个JwtTokenProvider类,实现了IJwtTokenProvider 接口。该服务类通过依赖注入方式注入了IConfiguration,从而可以在构造函数中读取JWtSetting配置。

  • JwtTokenProvider类中包含了生成访问令牌和验证令牌的方法,以及验证访问令牌和从访问令牌中获取用户主体的方法。其中,

    • GenerateAccessToken方法使用JwtSecurityTokenHandler来生成访问令牌,并设置了过期时间、签名等参数。

    • ValidateAccessToken方法验证访问令牌和从访问令牌中获取用户主体,验证是否过期。

用户信息加密

在JWT中,用户信息是以Claims的形式进行传递的,但默认情况下,这些信息是以明文的形式存储在令牌中的。为了保护用户信息的安全性,我们可以选择对用户信息进行加密。下面是一个简单的示例,演示如何在生成访问令牌时对用户信息进行加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public string GenerateAccessToken(UserTokenModel user)
{
// 设置Token的Claims
List<Claim> claims = new List<Claim>
{
new Claim(ZClaimTypes.UserName, user.UserName), //HttpContext.User.Identity.Name
new Claim(ZClaimTypes.UserId, user.UserId.ToString()),
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes)).ToUnixTimeSeconds()}"),
new Claim(ZClaimTypes.Expiration, DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes).ToString()),
};

if (user.RoleIds != null && user.RoleIds.Any())
{
claims.AddRange(user.RoleIds.Select(p => new Claim(ZClaimTypes.RoleIds, p.ToString())));
}
if (user.RoleNames != null && user.RoleNames.Any())
{
claims.AddRange(user.RoleNames.Select(p => new Claim(ZClaimTypes.Role, p)));
}

user.Claims = claims.ToArray();

// 生成Token的密钥
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SecretKey));

// 生成Token的签名证书
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256);

// 设置Token的过期时间
DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes);

// 创建Token
JwtSecurityToken token = new JwtSecurityToken(
_jwtConfig.Issuer,
_jwtConfig.Audience,
claims,
expires: expires,
signingCredentials: creds
);

// 生成Token字符串
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);

return tokenString;
}

在上面的示例中,我们使用了SigningCredentials类来设置加密的参数,包括加密密钥、加密算法等。这样生成的访问令牌在传递用户信息时会进行加密,增加了用户信息的安全性。

用户登录验证

在用户登录时,我们需要对用户提供的用户名和密码进行验证,并生成访问令牌和刷新令牌。下面是一个简单的示例,演示如何在ASP.NET Core中实现用户登录验证,并生成JWT令牌。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[HttpGet]
public async Task<string> Login()
{
UserTokenModel tokenModel = new UserTokenModel();
tokenModel.UserName = "test";
tokenModel.UserId = Guid.NewGuid().ToString("N");
var token = _jwtTokenProvider.GenerateAccessToken(tokenModel);

Response.Cookies.Append("x-access-token", token);
var claimsIdentity = new ClaimsIdentity(tokenModel.Claims, "Login");

AuthenticationProperties properties = new AuthenticationProperties();
properties.AllowRefresh = false;
properties.IsPersistent = true;
properties.IssuedUtc = DateTimeOffset.UtcNow;
properties.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), properties);

return token;
}

在上面的示例中,我们通过调用_jwtTokenProvider.GenerateAccessToken方法来生成访问令牌,并将访问令牌保存到Cookies中请求使用。

用户登录简单验证

在每次请求时,我们需要对访问令牌进行验证,以确保用户的身份和权限。下面是一个简单的示例,演示如何在ASP.NET Core中实现对访问令牌的简单验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[HttpGet("profile")]
[Authorize]
public IActionResult GetUserProfile()
{
// 获取当前用户的用户名
var username = ....;

// 根据用户名从数据库或其他持久化存储中获取用户信息
var userModel = ......;

if (userModel == null)
{
return NotFound(new { message = "userModel not found" });
}

// 返回用户信息给客户端
return Ok(new
{
username = userModel.Username,
email = userModel.Email
});
}

在上面的示例中,我们通过添加[Authorize]属性来标记需要验证访问令牌的API端点。当客户端发送请求时,ASP.NET Core会自动验证访问令牌的有效性,并将用户信息存储在UserTokenModel 对象中,以便我们在方法内部访问。

总结

本篇博文通过一个简单的案例,介绍了如何使用 C# .NET 实现 JWT 登录验证,并处理用户信息的加密、刷新 Token、各种验证规则等功能。