.NET 實(shí)現(xiàn) JWT 登錄驗(yàn)證
.NET 實(shí)現(xiàn)JWT登錄認(rèn)證
在ASP.NET Core應(yīng)用程序中,使用JWT進(jìn)行身份驗(yàn)證和授權(quán)已成為一種流行的方式。JWT是一種安全的方式,用于在客戶(hù)端和服務(wù)器之間傳輸用戶(hù)信息。
添加NuGet包
首先,我們需要添加一些NuGet包來(lái)支持JWT身份驗(yàn)證。在您的ASP.NET Core項(xiàng)目中,打開(kāi)Startup.cs文件,并在ConfigureServices方法中添加以下代碼:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
注冊(cè)
// ...
/// <summary>
/// jwt服務(wù)
/// </summary>
/// <param name="services"></param>
protected virtual void ServicesJwtToken(IServiceCollection services)
{
var config = configuration.GetSection("App:JWtSetting").Get<JwtSettings>(); // 從appsettings.json讀取JwtConfig配置
// 添加JWT身份驗(yàn)證服務(wù)
services.AddAuthentication(options =>
{
options.RequireAuthenticatedSignIn = true;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
//cokkie名稱(chēng)
options.Cookie.Name = "Z.BearerCokkie";
//cokkie過(guò)期時(shí)間
options.ExpireTimeSpan = TimeSpan.FromMinutes(config!.CokkieExpirationMinutes);
//cokkie啟用滑動(dòng)過(guò)期時(shí)間
options.SlidingExpiration = false;
options.LogoutPath = "/Home/Index";
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true, //是否驗(yàn)證Issuer
ValidIssuer = config!.Issuer, //發(fā)行人Issuer
ValidateAudience = true, //是否驗(yàn)證Audience
ValidAudience = config.Audience,//
ValidateIssuerSigningKey = true, //是否驗(yàn)證SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)), //SecurityKey
ValidateLifetime = true, //是否驗(yàn)證失效時(shí)間
ClockSkew = TimeSpan.FromSeconds(30), //過(guò)期時(shí)間容錯(cuò)值,解決服務(wù)器端時(shí)間不同步問(wèn)題(秒)
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值設(shè)置到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身份驗(yàn)證服務(wù),并設(shè)置了默認(rèn)的認(rèn)證方案為JwtBearerDefaults.AuthenticationScheme,這是JWT身份驗(yàn)證的默認(rèn)方案。
在AddJwtBearer方法中,我們通過(guò)GetSection方法從appsettings.json文件中讀取了一個(gè)名為JWtSetting的配置,其中包含了JWT的一些信息,例如簽發(fā)者(Issuer)、接收者(Audience)、秘鑰(SecretKey)等。這些信息將用于驗(yàn)證和生成JWT令牌。
在Configure方法中添加JWT認(rèn)證中間件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他中間件配置...
app.UseAuthentication();
app.UseAuthorization();
//...
}
配置appsettings.json
接下來(lái),我們需要在appsettings.json文件中配置JWT的相關(guān)信息。在您的ASP.NET Core項(xiàng)目中,找到appsettings.json文件,并添加以下配置:
{
// ...
"JWtSetting": {
"Issuer": "Z.NetWiki",
"Audience": "Z.NetWiki",
"SecretKey": "zhoulucky210@163.com",
"AccessTokenExpirationMinutes": 60,
"RefreshTokenExpirationMinutes": 1440,
"CokkieExpirationMinutes": 30
}
}
您可以根據(jù)自己的需求修改配置項(xiàng)的值。這里的AccessTokenExpirationMinutes和RefreshTokenExpirationMinutes分別表示訪問(wèn)令牌和刷新令牌的過(guò)期時(shí)間,單位為分鐘。
創(chuàng)建 JWT 設(shè)置類(lèi)
接下來(lái),我們需要?jiǎng)?chuàng)建一個(gè) C# 類(lèi)來(lái)表示 JWT 的配置項(xiàng),并使用 IOptions 接口將其注入到需要的地方。以下是一個(gè)示例的 JwtSettings 類(lèi):
public class JwtSettings
{
/// <summary>
/// 發(fā)行者
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 受眾
/// </summary>
public string Audience { get; set; }
/// <summary>
/// secretKey值
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 訪問(wèn)令牌過(guò)期時(shí)間
/// </summary>
public int AccessTokenExpirationMinutes { get; set; }
/// <summary>
/// cokkie過(guò)期時(shí)間
/// </summary>
public int CokkieExpirationMinutes { get; set; }
/// <summary>
/// 刷新令牌過(guò)期時(shí)間
/// </summary>
public int RefreshTokenExpirationMinutes { get; set; }
}
這個(gè)類(lèi)定義了與 appsettings.json 文件中的配置項(xiàng)相對(duì)應(yīng)的屬性。
用戶(hù)模型
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; }
}
實(shí)現(xiàn)JWT登錄認(rèn)證
現(xiàn)在,我們可以開(kāi)始實(shí)現(xiàn)JWT登錄認(rèn)證的邏輯。我們將創(chuàng)建一個(gè)JwtService類(lèi),用于生成和驗(yàn)證JWT令牌。在您的ASP.NET Core項(xiàng)目中,創(chuàng)建一個(gè)名為JwtService.cs的類(lèi)文件,然后添加以下代碼:
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("請(qǐng)先檢查appsetting中JWT配置");
}
public string GenerateAccessToken(UserTokenModel user)
{
// 設(shè)置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的簽名證書(shū)
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256);
// 設(shè)置Token的過(guò)期時(shí)間
DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes);
// 創(chuàng)建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);
}
這里我們創(chuàng)建了一個(gè)JwtTokenProvider類(lèi),實(shí)現(xiàn)了IJwtTokenProvider 接口。該服務(wù)類(lèi)通過(guò)依賴(lài)注入方式注入了IConfiguration,從而可以在構(gòu)造函數(shù)中讀取JWtSetting配置。
-
JwtTokenProvider類(lèi)中包含了生成訪問(wèn)令牌和驗(yàn)證令牌的方法,以及驗(yàn)證訪問(wèn)令牌和從訪問(wèn)令牌中獲取用戶(hù)主體的方法。其中,-
GenerateAccessToken方法使用JwtSecurityTokenHandler來(lái)生成訪問(wèn)令牌,并設(shè)置了過(guò)期時(shí)間、簽名等參數(shù)。 -
ValidateAccessToken方法驗(yàn)證訪問(wèn)令牌和從訪問(wèn)令牌中獲取用戶(hù)主體,驗(yàn)證是否過(guò)期。
-
用戶(hù)信息加密
在JWT中,用戶(hù)信息是以Claims的形式進(jìn)行傳遞的,但默認(rèn)情況下,這些信息是以明文的形式存儲(chǔ)在令牌中的。為了保護(hù)用戶(hù)信息的安全性,我們可以選擇對(duì)用戶(hù)信息進(jìn)行加密。下面是一個(gè)簡(jiǎn)單的示例,演示如何在生成訪問(wèn)令牌時(shí)對(duì)用戶(hù)信息進(jìn)行加密。
public string GenerateAccessToken(UserTokenModel user)
{
// 設(shè)置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的簽名證書(shū)
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256);
// 設(shè)置Token的過(guò)期時(shí)間
DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes);
// 創(chuàng)建Token
JwtSecurityToken token = new JwtSecurityToken(
_jwtConfig.Issuer,
_jwtConfig.Audience,
claims,
expires: expires,
signingCredentials: creds
);
// 生成Token字符串
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return tokenString;
}
在上面的示例中,我們使用了SigningCredentials類(lèi)來(lái)設(shè)置加密的參數(shù),包括加密密鑰、加密算法等。這樣生成的訪問(wèn)令牌在傳遞用戶(hù)信息時(shí)會(huì)進(jìn)行加密,增加了用戶(hù)信息的安全性。
用戶(hù)登錄驗(yàn)證
在用戶(hù)登錄時(shí),我們需要對(duì)用戶(hù)提供的用戶(hù)名和密碼進(jìn)行驗(yàn)證,并生成訪問(wèn)令牌和刷新令牌。下面是一個(gè)簡(jiǎn)單的示例,演示如何在ASP.NET Core中實(shí)現(xiàn)用戶(hù)登錄驗(yàn)證,并生成JWT令牌。
[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;
}
在上面的示例中,我們通過(guò)調(diào)用_jwtTokenProvider.GenerateAccessToken方法來(lái)生成訪問(wèn)令牌,并將訪問(wèn)令牌保存到Cookies中請(qǐng)求使用。
用戶(hù)登錄簡(jiǎn)單驗(yàn)證
在每次請(qǐng)求時(shí),我們需要對(duì)訪問(wèn)令牌進(jìn)行驗(yàn)證,以確保用戶(hù)的身份和權(quán)限。下面是一個(gè)簡(jiǎn)單的示例,演示如何在ASP.NET Core中實(shí)現(xiàn)對(duì)訪問(wèn)令牌的簡(jiǎn)單驗(yàn)證。
[HttpGet("profile")]
[Authorize]
public IActionResult GetUserProfile()
{
// 獲取當(dāng)前用戶(hù)的用戶(hù)名
var username = ....;
// 根據(jù)用戶(hù)名從數(shù)據(jù)庫(kù)或其他持久化存儲(chǔ)中獲取用戶(hù)信息
var userModel = ......;
if (userModel == null)
{
return NotFound(new { message = "userModel not found" });
}
// 返回用戶(hù)信息給客戶(hù)端
return Ok(new
{
username = userModel.Username,
email = userModel.Email
});
}
在上面的示例中,我們通過(guò)添加[Authorize]屬性來(lái)標(biāo)記需要驗(yàn)證訪問(wèn)令牌的API端點(diǎn)。當(dāng)客戶(hù)端發(fā)送請(qǐng)求時(shí),ASP.NET Core會(huì)自動(dòng)驗(yàn)證訪問(wèn)令牌的有效性,并將用戶(hù)信息存儲(chǔ)在UserTokenModel 對(duì)象中,以便我們?cè)诜椒▋?nèi)部訪問(wèn)。
總結(jié)
本篇博文通過(guò)一個(gè)簡(jiǎn)單的案例,介紹了如何使用 C# .NET 實(shí)現(xiàn) JWT 登錄驗(yàn)證,并處理用戶(hù)信息的加密、刷新 Token、各種驗(yàn)證規(guī)則等功能。

浙公網(wǎng)安備 33010602011771號(hào)