Asp.Net Core 6 之 基于Jwt 的身份认证
程序集
Microsoft.AspNetCore.Authentication.JwtBearer;
身份认证服务器
jwt配置类: JWTTokenOptions.cs
public class JWTTokenOptions
{
public string Audience
{
get;
set;
}
public string SecurityKey
{
get;
set;
}
//public SigningCredentials Credentials
//{
// get;
// set;
//}
public string Issuer
{
get;
set;
}
}
apppsetting.json
"JWTTokenOptions": {
"Audience": "http://localhost:5200",
"Issuer": "http://localhost:5200",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADxxxx"
}
生成 token
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private ICustomJWTService _iJWTService = null;
public AuthenticationController(ICustomJWTService customJWTService)
{
_iJWTService = customJWTService;
}
[Route("Login")]
[HttpPost]
public string Login(string name, string password)
{
//在这里需要去数据库中做数据验证
if ("admin".Equals(name) && "123456".Equals(password))
{
//就应该生成Token
string token = this._iJWTService.GetToken(name, password);
return JsonConvert.SerializeObject(new
{
result = true,
token
});
}
else
{
return JsonConvert.SerializeObject(new
{
result = false,
token = ""
});
}
对称加密
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public class CustomHSJWTService : ICustomJWTService
{
#region Option注入
private readonly JWTTokenOptions _JWTTokenOptions;
public CustomHSJWTService(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
{
this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
}
#endregion
/// <summary>
/// 用户登录成功以后,用来生成Token的方法
/// </summary>
/// <param name="UserName"></param>
/// <returns></returns>
public string GetToken(string UserName, string password)
{
#region 有效载荷,大家可以自己写,爱写多少写多少;尽量避免敏感信息
var claims = new[]
{
new Claim(ClaimTypes.Name, UserName),
new Claim(ClaimTypes.Role, "teache0"),
new Claim("NickName",UserName),
new Claim("Role","Administrator"),//传递其他信息
new Claim("ABCC","ABCC"),
new Claim("Student","甜酱油")
};
//需要加密:需要加密key:
//Nuget引入:Microsoft.IdentityModel.Tokens
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_JWTTokenOptions.SecurityKey));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//Nuget引入:System.IdentityModel.Tokens.Jwt
JwtSecurityToken token = new JwtSecurityToken(
issuer: _JWTTokenOptions.Issuer,
audience: _JWTTokenOptions.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(5),//5分钟有效期
signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken;
#endregion
}
}
非对称加密
public class CustomRSSJWTervice : ICustomJWTService
{
#region Option注入
private readonly JWTTokenOptions _JWTTokenOptions;
public CustomRSSJWTervice(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
{
this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
}
#endregion
public string GetToken(string userName, string password)
{
#region 使用加密解密Key 非对称
string keyDir = Directory.GetCurrentDirectory();
if (RSAHelper.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
{
keyParams = RSAHelper.GenerateAndSaveKey(keyDir);
}
#endregion
//string jtiCustom = Guid.NewGuid().ToString();//用来标识 Token
Claim[] claims = new[]
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Role,"admin"),
new Claim("password",password)
};
SigningCredentials credentials = new SigningCredentials(new RsaSecurityKey(keyParams), SecurityAlgorithms.RsaSha256Signature);
var token = new JwtSecurityToken(
issuer: this._JWTTokenOptions.Issuer,
audience: this._JWTTokenOptions.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(60),//5分钟有效期
signingCredentials: credentials);
var handler = new JwtSecurityTokenHandler();
string tokenString = handler.WriteToken(token);
return tokenString;
}
}
受保护WebApi
执行身份验证
Program.cs
//跨域
builder.Services.AddCors(policy =>
{
policy.AddPolicy("CorsPolicy", opt => opt
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"));
});
{
#region jwt校验 HS(对称加密)
{
//第二步,增加鉴权逻辑
JWTTokenOptions tokenOptions = new JWTTokenOptions();
builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//Scheme
.AddJwtBearer(options => //这里是配置的鉴权的逻辑
{
options.TokenValidationParameters = new TokenValidationParameters
{
//JWT有一些默认的属性,就是给鉴权时就可以筛选了
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = tokenOptions.Audience,//
ValidIssuer = tokenOptions.Issuer,//Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey))//拿到SecurityKey
};
options.Events = new JwtBearerEvents
{
//通过身份验证后,回调
//可以在这里进行登录,设置 HttpContext.Principal
OnTokenValidated = async ctx =>
{
string userName = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
/*
string userName = ctx.Principal.FindUserByUserName(userName);
User user = FindFromDb()
ctx.Principal =
*/
var claims = new List<Claim>()//鉴别你是谁,相关信息
{
new Claim(ClaimTypes.Name,userName),
new Claim("Userid","1"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim(ClaimTypes.Role,"User"),
new Claim(ClaimTypes.Email,$"xxxx@163.com"),
new Claim("password",password),//可以写入任意数据
new Claim("Account","Administrator"),
new Claim("role","admin"),
new Claim("QQ","xxxx")
};
ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
ctx.Principal = userPrincipal;
}
};
});
}
#endregion
}
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
重点: 使用
options.Events = new JwtBearerEvents
{
//通过身份验证后,回调
//可以在这里进行登录,设置 HttpContext.Principal
OnTokenValidated = async ctx =>
{
string userName = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
/*
string userName = ctx.Principal.FindUserByUserName(userName);
User user = FindFromDb()
ctx.Principal =
*/
var claims = new List<Claim>()//鉴别你是谁,相关信息
{
new Claim(ClaimTypes.Name,userName),
new Claim("Userid","1"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim(ClaimTypes.Role,"User"),
new Claim(ClaimTypes.Email,$"xxxx@163.com"),
new Claim("password",password),//可以写入任意数据
new Claim("Account","Administrator"),
new Claim("role","admin"),
new Claim("QQ","xxxx")
};
ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
ctx.Principal = userPrincipal;
}
在通过身份验证后,回调设置当前用户信息,
ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
ctx.Principal = userPrincipal;
以便无缝对接当前基于用户 Claime 设置的其它授权策略,方案等。
相当于 使用 token 身份验证成功后,再执行 UserName + Password 登录的逻辑.
当然,这一步根据实际情况取舍。
保护端点,Contronller或 Action
public class SecondController : ControllerBase
{
[HttpGet]
[Authorize(AuthenticationSchemes= JwtBearerDefaults.AuthenticationScheme)]
public object GetData()
{
Console.WriteLine("请求到了~~");
return new
{
Id = 123,
Name = "Richard"
};
}
Artisan.Zo 中的示例
配置类
JwtOptions
public class JwtOptions
{
public string Audience { get; set; }
public string SecurityKey { get; set; }
public string Issuer { get; set; }
public int ExpirationTime { get; set; }
public bool RequireHttps { get; set; }
Artisan.Zo.Applicaiton
AccountAppService.cs
using Artisan.Zo.Auths.Jwt;
using Artisan.Zo.Dtos;
using IdentityModel;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using AspNetCore = Microsoft.AspNetCore.Identity;
namespace Artisan.Zo.Account
{
public class AccountAppService : ZoAppServiceBase, IAccountAppService
{
private readonly IdentityUserManager userManager;
private readonly AspNetCore.SignInManager<IdentityUser> signInManager;
//jwt
private readonly JwtOptions jwtOptions;
private readonly IJwtGenerator jwtGenerator;
public AccountAppService(
IdentityUserManager userManager,
AspNetCore.SignInManager<IdentityUser> signInManager,
IOptionsSnapshot<JwtOptions> jwtOptions)
{
this.userManager = userManager;
this.signInManager = signInManager;
this.jwtOptions = jwtOptions.Value;
}
public virtual async Task<LoginDto> LoginAsync(LoginInput input)
{
var result = await signInManager.PasswordSignInAsync(input.Name, input.Password, false, true);
if (result.Succeeded)
{
var user = await userManager.FindByNameAsync(input.Name);
return await BuildLoginResult(user);
}
else
{
if (result.IsLockedOut)
{
throw new UserFriendlyException(L["DisplayName:LockoutEnabled"]);
}else
{
throw new UserFriendlyException(L["InvalidUserNameOrPassword"]);
}
}
}
private async Task<LoginDto> BuildLoginResult(IdentityUser user)
{
if (user.LockoutEnabled) {
throw new UserFriendlyException(L["DisplayName:LockoutEnabled"]);
}
var roles = await userManager.GetRolesAsync(user);
var token = jwtGenerator.GenerateToken(new CreateJwtTokenInput { IdentityUser = user, JwtOptions = jwtOptions, Roles = roles});
var LoginDto = ObjectMapper.Map<IdentityUser, LoginDto>(user);
LoginDto.Token = token;
LoginDto.Roles = roles;
return LoginDto;
}
}
}
JwtHsGenerator.cs 对称加密算法token
using IdentityModel;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
namespace Artisan.Zo.Auths.Jwt
{
/// <summary>
/// 对称加密
/// </summary>
public class JwtHsGenerator : IJwtGenerator
{
public string GenerateToken(CreateJwtTokenInput input)
{
IdentityUser user = input.IdentityUser;
IList<string> roles = input.Roles;
JwtOptions jwtOptions = input.JwtOptions;
var dateNow = DateTime.Now;
var expirationTime = dateNow + TimeSpan.FromHours(jwtOptions.ExpirationTime);
var key = Encoding.ASCII.GetBytes(jwtOptions.SecurityKey);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims),
Expires = expirationTime,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateToken(tokenDescriptor);
return handler.WriteToken(token);
}
//public string GenerateToken(CreateJwtTokenInput input)
//{
// IdentityUser user = input.IdentityUser;
// IList<string> roles = input.Roles;
// JwtOptions jwtOptions = input.JwtOptions;
// var dateNow = DateTime.Now;
// var expirationTime = dateNow + TimeSpan.FromHours(jwtOptions.ExpirationTime);
// var key = Encoding.ASCII.GetBytes(jwtOptions.SecurityKey);
// var claims = new List<Claim>
// {
// new Claim(JwtClaimTypes.Audience, jwtOptions.Audience),
// new Claim(JwtClaimTypes.Issuer, jwtOptions.Issuer),
// new Claim(AbpClaimTypes.UserId, user.Id.ToString()),
// new Claim(AbpClaimTypes.Name, user.Name),
// new Claim(AbpClaimTypes.UserName, user.UserName),
// new Claim(AbpClaimTypes.Email, user.Email),
// new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())
// };
// if (roles != null && roles.Any())
// {
// foreach (var item in roles)
// {
// claims.Add(new Claim(JwtClaimTypes.Role, item));
// }
// }
// var tokenDescriptor = new SecurityTokenDescriptor()
// {
// Subject = new ClaimsIdentity(claims),
// Expires = expirationTime,
// SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
// };
// var handler = new JwtSecurityTokenHandler();
// var token = handler.CreateToken(tokenDescriptor);
// return handler.WriteToken(token);
//}
}
}
WebApi.Host
appsetting.json
"Auth": {
"Jwt": {
//客户端标识
"Audience": "ArtisanZoApiHost",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNAD=",
//签发者
"Issuer": "ArtisanZo",
//过期时间,单位:小时
"ExpirationTime": 24,
"RequireHttps": false
}
private void ConfigureOptions(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.Configure<JwtOptions>(context.Services.GetConfiguration()
.GetSection("Auth:Jwt"));
}
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
//
//context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
// .AddJwtBearer(options =>
// {
// options.Authority = configuration["AuthServer:Authority"];
// options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
// options.Audience = "Zo";
// });
var jwtOption = configuration.GetSection("Auth:Jwt").Get<JwtOptions>();
context.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>{
options.RequireHttpsMetadata = jwtOption.RequireHttps;
options.SaveToken = true;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
//ClockSkew = TimeSpan.Zero,
ValidIssuer = jwtOption.Issuer,
ValidAudience = jwtOption.Audience,
IssuerSigningKey =
new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(jwtOption.SecurityKey))
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async ctx =>
{
var services = ctx.HttpContext.RequestServices;
var userManager = services.GetRequiredService<IdentityUserManager>();
var signInManager = services.GetRequiredService<AbpSignInManager>();
string name = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
IdentityUser identityUser = await userManager.FindByNameAsync(name);
//构建jwt令牌和AspNet Core Identity数据的桥梁,
//确保像基于角色的授权这样的特性无缝地工作
ctx.Principal = await signInManager.CreateUserPrincipalAsync(identityUser);
}
};
});
}