.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 protected virtual void ServicesJwtToken (IServiceCollection services ) { var config = configuration.GetSection("App:JWtSetting" ).Get<JwtSettings>(); services.AddAuthentication(options => { options.RequireAuthenticatedSignIn = true ; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddCookie(options => { options.Cookie.Name = "Z.BearerCokkie" ; options.ExpireTimeSpan = TimeSpan.FromMinutes(config!.CokkieExpirationMinutes); options.SlidingExpiration = false ; options.LogoutPath = "/Home/Index" ; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true , ValidIssuer = config!.Issuer, ValidateAudience = true , ValidAudience = config.Audience, ValidateIssuerSigningKey = true , IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)), ValidateLifetime = true , ClockSkew = TimeSpan.FromSeconds(30 ), RequireExpirationTime = true , SaveSigninToken = true , }; options.Events = new JwtBearerEvents { OnMessageReceived = async context => { var token = context.Request.Cookies["access_token" ]; if (!string .IsNullOrEmpty(token)) { context.Token = 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 } }
您可以根据自己的需求修改配置项的值。这里的AccessTokenExpirationMinutes
和RefreshTokenExpirationMinutes
分别表示访问令牌和刷新令牌的过期时间,单位为分钟。
创建 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 { public string Issuer { get ; set ; } public string Audience { get ; set ; } public string SecretKey { get ; set ; } public int AccessTokenExpirationMinutes { get ; set ; } public int CokkieExpirationMinutes { get ; set ; } 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 ) { List<Claim> claims = new List<Claim> { new Claim(ZClaimTypes.UserName, user.UserName), 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(); SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SecretKey)); SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256); DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes); JwtSecurityToken token = new JwtSecurityToken( _jwtConfig.Issuer, _jwtConfig.Audience, claims, expires: expires, signingCredentials: creds ); 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
配置。
用户信息加密
在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 ) { List<Claim> claims = new List<Claim> { new Claim(ZClaimTypes.UserName, user.UserName), 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(); SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SecretKey)); SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.Aes128CbcHmacSha256); DateTime expires = DateTime.Now.AddMinutes(_jwtConfig.AccessTokenExpirationMinutes); JwtSecurityToken token = new JwtSecurityToken( _jwtConfig.Issuer, _jwtConfig.Audience, claims, expires: expires, signingCredentials: creds ); 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、各种验证规则等功能。