学习ASP.NET Core Blazor编程系列二十八——JWT登录(2)
接上文学习ASP.NET Core Blazor编程系列二十七——JWT登录(1) ,我们继续实现功能。
5.在Visual Studio 2022的解决方案资源管理器中,鼠标左键选中“Utils”文件夹,右键单击,在弹出菜单中选择“添加—>新建项”,在弹出对话框中,选择“接口”,并将接口命名为“IJWTHelper”。如下图。并添加如下代码:
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
namespace BlazorAppDemo.Utils
{
public interface IJWTHelper
{
string CreateJwtToken<T>(T user);
T GetToken<T>(string Token);
IEnumerable<Claim> ParseToken(string token);
string? ValidateJwtToken(IEnumerable<Claim> jwtToken);
TokenValidationParameters ValidParameters();
}
}
6.在Visual Studio 2022的解决方案资源管理器中,鼠标左键选中“Utils”文件夹,右键单击,在弹出菜单中选择“添加—>类”,在弹出对话框中,并将类命名为“JWTHelper”。并继承IJWTHelper接口,添加如下代码:
using BlazorAppDemo.Models; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Claims; using System.Text; namespace BlazorAppDemo.Utils { public class JWTHelper : IJWTHelper { private readonly IConfiguration _configuration; private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; public JWTHelper(IConfiguration configuration, JwtSecurityTokenHandler jwtSecurityTokenHandler) { _configuration = configuration; _jwtSecurityTokenHandler = jwtSecurityTokenHandler; } /// <summary> /// 创建加密JwtToken /// </summary> /// <param name="user"></param> /// <returns></returns> public string CreateJwtToken<T>(T user) { var signingAlogorithm = SecurityAlgorithms.HmacSha256; var claimList = this.CreateClaimList(user); //Signature //取出私钥并以utf8编码字节输出 var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]); //使用非对称算法对私钥进行加密 var signingKey = new SymmetricSecurityKey(secretByte); //使用HmacSha256来验证加密后的私钥生成数字签名 var signingCredentials = new SigningCredentials(signingKey, signingAlogorithm); //在 RFC 7519 规范中(Section#4),一共定义了 7 个预设的 Claims。 //生成Token var Token = new JwtSecurityToken( issuer: _configuration["Authentication:Issuer"], //发布者 audience: _configuration["Authentication:Audience"], //接收者 claims: claimList, //存放的用户信息 notBefore: DateTime.UtcNow, //发布时间 expires: DateTime.UtcNow.AddDays(1), //有效期设置为1天 signingCredentials //数字签名 ); //生成字符串token var TokenStr = new JwtSecurityTokenHandler().WriteToken(Token); return TokenStr; } public T GetToken<T>(string Token) { Type t = typeof(T); object objA = Activator.CreateInstance(t); var b = _jwtSecurityTokenHandler.ReadJwtToken(Token); foreach (var item in b.Claims) { PropertyInfo _Property = t.GetProperty(item.Type); if (_Property != null && _Property.CanRead) { _Property.SetValue(objA, item.Value, null); } } return (T)objA; } /// <summary> /// 创建包含用户信息的CalimList /// </summary> /// <param name="authUser"></param> /// <returns></returns> private List<Claim> CreateClaimList<T>(T authUser) { var Class = typeof(UserInfo); List<Claim> claimList = new List<Claim>(); foreach (var item in Class.GetProperties()) { if (item.Name == "Password") { continue; } claimList.Add(new Claim(ClaimTypes.Name, Convert.ToString(item.GetValue(authUser)))); } return claimList; } /// /// <summary> /// 从令牌中获取数据声明 /// </summary> /// <param name="token">令牌</param> /// <returns></returns> public IEnumerable<Claim> ParseToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var validateParameter = ValidParameters(); token = token.Replace("Bearer ", ""); try { tokenHandler.ValidateToken(token, validateParameter, out SecurityToken validatedToken); var jwtToken = tokenHandler.ReadJwtToken(token); return jwtToken.Claims; } catch (Exception ex) { Console.WriteLine(ex.Message); return null; } }
/// <summary>
/// jwt token校验
/// </summary>
/// <param name="jwtToken"></param>
/// <returns></returns>
public string? ValidateJwtToken(IEnumerable<Claim> jwtToken)
{
try
{
var UserId = jwtToken.FirstOrDefault(x => x.Type == "UserId")?.Value;
var UserName = jwtToken.FirstOrDefault(x => x.Type == "UserName")?.Value;
return UserId;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
/// <summary>
/// 验证Token
/// </summary>
/// <returns></returns>
public TokenValidationParameters ValidParameters()
{
return new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = _configuration["Authentication:Issuer"],
ValidAudience = _configuration["Authentication:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["Authentication:SecretKey"])),
ValidateLifetime = true,//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ClockSkew = TimeSpan.FromSeconds(30),
RequireExpirationTime = true,//过期时间
};
}
} }
7. 在Visual Studio 2022的解决方案资源管理器中,使用鼠标左键双击“Auth”文件夹中的“ImitateAuthStateProvider.cs”文件,在文本编辑器中打开,对代码进行修改。具体代码如下:
using BlazorAppDemo.Models;
using BlazorAppDemo.Utils;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace BlazorAppDemo.Auth
{
public class ImitateAuthStateProvider : AuthenticationStateProvider
{
private readonly IJWTHelper jwt;
public ImitateAuthStateProvider(IJWTHelper _jwt) => jwt = _jwt; //DI
bool isLogin = false;
string token = string.Empty;
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (isLogin)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,"user"),
new Claim(ClaimTypes.Role, "admin")
};
var anonymous = new ClaimsIdentity(claims, "testAuthType");
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
}
else
{
var anonymous = new ClaimsIdentity();
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
}
}
public void Login(UserInfo request)
{
//1.验证用户账号密码是否正确
if (request == null)
{
isLogin=false;
}
if (request.UserName == "user" && request.Password == "111111")
{
isLogin = true;
token= jwt.CreateJwtToken<UserInfo>(request);
Console.WriteLine($"JWT Token={token}");
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}
}
8.在Visual Studio 2022的解决方案管理器中,使用鼠标左键,双击Program.cs文件,将之在文本编辑器中打开,将我们写的JWTHelper与JwtSecurityTokenHandler、使用DI方式注入,添加JWT认证服务。具体代码如下:
using BlazorAppDemo.Data;
using BlazorAppDemo.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Components.Authorization;
using BlazorAppDemo.Auth;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using BlazorAppDemo.Utils;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
System.Console.WriteLine(ConfigHelper.Configuration["ConnectionStrings:BookContext"]);
builder.Services.AddDbContextFactory<BookContext>(opt =>
opt.UseSqlServer(ConfigHelper.Configuration["ConnectionStrings:BookContext"]));
//builder.Services.AddScoped<AuthenticationStateProvider, ImitateAuthStateProvider>();
builder.Services.AddScoped<ImitateAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(implementationFactory =>
implementationFactory.GetRequiredService<ImitateAuthStateProvider>());
builder.Services.AddScoped<JwtSecurityTokenHandler>();
//JWT
//JWT认证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
//取出私钥
var secretByte = Encoding.UTF8.GetBytes(builder.Configuration["Authentication:SecretKey"]);
options.TokenValidationParameters = new TokenValidationParameters()
{
//验证发布者
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Authentication:Issuer"],
//验证接收者
ValidateAudience = true,
ValidAudience = builder.Configuration["Authentication:Audience"],
//验证是否过期
ValidateLifetime = true,
//验证私钥
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
});
//自己写的Jwt 扩展
builder.Services.AddScoped<IJWTHelper,JWTHelper>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
Console.WriteLine("数据库开始初始化。");
var context = services.GetRequiredService<BookContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
Console.WriteLine("数据库初始化结束。");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "数据库数据初始化错误.");
}
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.UseAuthentication();
app.UseAuthorization();
app.Run();
10.我们在用户名输入框中输入用户名,在密码输入框中输入密码,点击“登录”按钮,进行模拟登录。我们进入了系统。并且生成了JWT Token。如下图。