增加安全组件JwtContext,通过IServiceCollection.AddFalconJWT注册。
This commit is contained in:
parent
37a6942020
commit
7157dac6bf
51
Falcon.SugarApi.Test/JwtTokenBuilderTest.cs
Normal file
51
Falcon.SugarApi.Test/JwtTokenBuilderTest.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Falcon.SugarApi.JWT;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Falcon.SugarApi.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// JwtTokenBuilderTest
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class JwtTokenBuilderTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Token获取测试
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetTokenTest() {
|
||||
var loginTime = DateTime.Now;
|
||||
var playload = new LoginUserInfo {
|
||||
UserName = "abdc",
|
||||
LoginTime = loginTime,
|
||||
Roles = new List<string> { "admin", "user" },
|
||||
};
|
||||
if (new JwtTokenBuilder().TryGetToken(playload, out var token, out var exception)) {
|
||||
Console.WriteLine("token:{0}", token);
|
||||
}
|
||||
else {
|
||||
Console.WriteLine(exception.ToString());
|
||||
Assert.Fail("获取token失败");
|
||||
}
|
||||
Assert.IsNotNull(token);
|
||||
if (new JwtTokenBuilder().TryGetPlayload(token, out var pl, out var exception1)) {
|
||||
|
||||
}
|
||||
else {
|
||||
Console.WriteLine(exception1.ToString());
|
||||
Assert.Fail("获取Playload失败");
|
||||
}
|
||||
Assert.IsNotNull(pl);
|
||||
Assert.AreEqual(playload.UserName, pl.UserName);
|
||||
Assert.IsTrue(pl.Roles != null);
|
||||
Assert.IsTrue(pl.Roles.Count == 2);
|
||||
Assert.IsTrue(pl.Roles.Any(m => m == "admin"));
|
||||
Assert.IsTrue(pl.Roles.Any(m => m == "user"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
namespace Falcon.SugarApi.Encryption
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Falcon.SugarApi.Encryption
|
||||
{
|
||||
/// <summary>
|
||||
/// AES加密算法配置
|
||||
/// </summary>
|
||||
public class AESConfig
|
||||
public class AESConfig : IOptions<AESConfig>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public AESConfig Value => this;
|
||||
|
||||
private int keyLength = 32;
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,5 +21,6 @@
|
|||
/// 秘钥字符表
|
||||
/// </summary>
|
||||
public string KeyChars { get; set; } = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789,.!/*\";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<!--<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />-->
|
||||
<PackageReference Include="JWT" Version="9.0.3" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.3.27" />
|
||||
<PackageReference Include="SugarRedis" Version="1.3.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.4.0" />
|
||||
|
|
42
Falcon.SugarApi/JWT/AESTokenbuilder.cs
Normal file
42
Falcon.SugarApi/JWT/AESTokenbuilder.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using Falcon.SugarApi.Encryption;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用AES算法生成Token
|
||||
/// </summary>
|
||||
public class AESTokenbuilder : IJwtTokenBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// AES加密算法
|
||||
/// </summary>
|
||||
public IAESEncryption AES { get; set; }
|
||||
/// <summary>
|
||||
/// jwt生成参数
|
||||
/// </summary>
|
||||
public JwtContext Jwt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造AES的Token
|
||||
/// </summary>
|
||||
/// <param name="config">AES参数</param>
|
||||
/// <param name="jwt">jwt生成参数</param>
|
||||
public AESTokenbuilder(AESConfig config, JwtContext jwt) {
|
||||
Jwt = jwt;
|
||||
AES = new AESProvider(config);
|
||||
}
|
||||
|
||||
/// <inheritdoc>/
|
||||
public LoginUserInfo GetPlayload(string token) {
|
||||
token.ThrowNullExceptionWhenNull();
|
||||
return this.AES.Decrypt<LoginUserInfo>(Jwt.SecKey, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc>/
|
||||
public string GetToken(LoginUserInfo playload) {
|
||||
playload.ThrowNullExceptionWhenNull();
|
||||
return this.AES.Encrypt(Jwt.SecKey, playload);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
59
Falcon.SugarApi/JWT/ApiAuthorizationAttribute.cs
Normal file
59
Falcon.SugarApi/JWT/ApiAuthorizationAttribute.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class ApiAuthorizationAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户需要具有的角色
|
||||
/// </summary>
|
||||
public List<string> Roles { get; set; } = new List<string>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnAuthorization(AuthorizationFilterContext context) {
|
||||
if (context.Filters.Any(f => f is IAllowAnonymousFilter)) {
|
||||
return;
|
||||
}
|
||||
var option = context.HttpContext.RequestServices.GetRequiredService<JwtContext>();
|
||||
var key = option?.AuthHeaderKey;
|
||||
key.ThrowNullExceptionWhenNull();
|
||||
var token = context.HttpContext.Request.Headers[key].ToString();
|
||||
if (token.IsNullOrEmpty()) {
|
||||
Unauthorized(context);
|
||||
return;
|
||||
}
|
||||
var jwt = option?.JwtTokenBuilder;
|
||||
jwt.ThrowNullExceptionWhenNull();
|
||||
var user = jwt.GetPlayload(token);
|
||||
var userLogin = option?.UserLogin;
|
||||
if (userLogin != null && !userLogin.CheckUserLogin(user)) {
|
||||
Unauthorized(context);
|
||||
return;
|
||||
}
|
||||
if (this.Roles != null && this.Roles.Count > 0 && !userLogin.UserInRoles(user, this.Roles)) {
|
||||
Unauthorized(context);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回授权失败
|
||||
/// </summary>
|
||||
/// <param name="context">上下文</param>
|
||||
private static void Unauthorized(AuthorizationFilterContext context) {
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
}
|
||||
}
|
||||
}
|
22
Falcon.SugarApi/JWT/IJwtTokenBuilder.cs
Normal file
22
Falcon.SugarApi/JWT/IJwtTokenBuilder.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// jwt token 生成器
|
||||
/// </summary>
|
||||
public interface IJwtTokenBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取Token负载
|
||||
/// </summary>
|
||||
/// <param name="token">登录Token</param>
|
||||
/// <returns>负载信息</returns>
|
||||
LoginUserInfo GetPlayload(string token);
|
||||
/// <summary>
|
||||
/// 获取Token
|
||||
/// </summary>
|
||||
/// <param name="playload">负载</param>
|
||||
/// <returns>Token对象</returns>
|
||||
string GetToken(LoginUserInfo playload);
|
||||
}
|
||||
}
|
56
Falcon.SugarApi/JWT/IServiceCollectionExtend.cs
Normal file
56
Falcon.SugarApi/JWT/IServiceCollectionExtend.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using Falcon.SugarApi.Encryption;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务扩展
|
||||
/// </summary>
|
||||
public static class IServiceCollectionExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册Falcon.SqlSugar.JWT
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddFalconJWT(this IServiceCollection services)
|
||||
=> services.AddFalconJWT(null);
|
||||
|
||||
/// <summary>
|
||||
/// 注册Falcon.SqlSugar.JWT.JwtContext,并配置相关参数
|
||||
/// <para>消费端通过JwtContext注入</para>
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="OptionBuilder">参数创建器</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddFalconJWT(this IServiceCollection services, Action<JwtContext>? OptionBuilder) {
|
||||
services.AddSingleton<JwtContext>(sp => {
|
||||
var option = new JwtContext();
|
||||
var jtb = sp.GetService<IJwtTokenBuilder>();
|
||||
if (jtb == null) {
|
||||
var aesc = sp.GetService<AESConfig>() ?? new AESConfig();
|
||||
jtb = new AESTokenbuilder(aesc, option);
|
||||
}
|
||||
option.JwtTokenBuilder = jtb;
|
||||
|
||||
var ulj = sp.GetService<IUserLogin>();
|
||||
if (ulj != null) {
|
||||
option.UserLogin = ulj;
|
||||
}
|
||||
|
||||
OptionBuilder?.Invoke(option);
|
||||
|
||||
if (option.UserLogin == null) {
|
||||
throw new Exception("必须为JwtOptions提供UserLogin属性!");
|
||||
}
|
||||
if (option.JwtTokenBuilder == null) {
|
||||
throw new Exception("必须为JwtOptions提供JwtTokenBuilder属性!");
|
||||
}
|
||||
return option;
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
39
Falcon.SugarApi/JWT/IUserLogin.cs
Normal file
39
Falcon.SugarApi/JWT/IUserLogin.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户登录验证接口
|
||||
/// </summary>
|
||||
public interface IUserLogin
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录是否成功
|
||||
/// </summary>
|
||||
/// <param name="login">登录凭据</param>
|
||||
/// <returns>成功返回用户信息,否则返回null</returns>
|
||||
LoginUserInfo? Login(LoginDto login);
|
||||
|
||||
/// <summary>
|
||||
/// 用户登出
|
||||
/// </summary>
|
||||
/// <param name="userInfo">用户名</param>
|
||||
/// <returns>True成功,False失败</returns>
|
||||
bool LogOut(LoginUserInfo userInfo);
|
||||
|
||||
/// <summary>
|
||||
/// 登录用户验证。检查客户端提供的已登录用户是否仍然处于登录中。
|
||||
/// </summary>
|
||||
/// <param name="userInfo">登录凭据</param>
|
||||
/// <returns>登录中返回True,否则返回False</returns>
|
||||
bool CheckUserLogin(LoginUserInfo userInfo);
|
||||
|
||||
/// <summary>
|
||||
/// 用户是否具有某个角色
|
||||
/// </summary>
|
||||
/// <param name="userInfo">用户信息</param>
|
||||
/// <param name="roles">需要具有的角色组</param>
|
||||
/// <returns>True具有,False不具有</returns>
|
||||
bool UserInRoles(LoginUserInfo userInfo, List<string> roles);
|
||||
}
|
||||
}
|
29
Falcon.SugarApi/JWT/JwtContext.cs
Normal file
29
Falcon.SugarApi/JWT/JwtContext.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// JWT认证上下文
|
||||
/// </summary>
|
||||
public class JwtContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证头的键值
|
||||
/// </summary>
|
||||
public string AuthHeaderKey { get; set; } = "auth";
|
||||
|
||||
/// <summary>
|
||||
/// 用户Token加密的秘钥
|
||||
/// </summary>
|
||||
public string SecKey { get; set; } = "fefafwefwf464664f64e64f63";
|
||||
|
||||
/// <summary>
|
||||
/// jwtToken生成器
|
||||
/// </summary>
|
||||
public IJwtTokenBuilder? JwtTokenBuilder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录管理
|
||||
/// </summary>
|
||||
public IUserLogin? UserLogin { get; set; }
|
||||
|
||||
}
|
||||
}
|
64
Falcon.SugarApi/JWT/JwtTokenBuilder.cs
Normal file
64
Falcon.SugarApi/JWT/JwtTokenBuilder.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using JWT;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Serializers;
|
||||
using Microsoft.AspNetCore.Server.IIS.Core;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// jwt token 生成器
|
||||
/// </summary>
|
||||
public class JwtTokenBuilder : IJwtTokenBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用默认配置实例化
|
||||
/// </summary>
|
||||
public JwtTokenBuilder() : this(new JwtContext()) { }
|
||||
|
||||
/// <summary>
|
||||
/// 实例化
|
||||
/// </summary>
|
||||
/// <param name="options">JWT认证配置</param>
|
||||
public JwtTokenBuilder(JwtContext options) {
|
||||
Options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JWT认证配置
|
||||
/// </summary>
|
||||
public JwtContext Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取Token
|
||||
/// </summary>
|
||||
/// <param name="playload">负载</param>
|
||||
/// <returns>Token对象</returns>
|
||||
public string GetToken(LoginUserInfo playload) {
|
||||
playload = playload ?? throw new ArgumentNullException(nameof(playload));
|
||||
var key = Encoding.UTF8.GetBytes(this.Options.SecKey);
|
||||
var algorithm = new HMACSHA256Algorithm();
|
||||
var serializer = new JsonNetSerializer();
|
||||
var urlEncoder = new JwtBase64UrlEncoder();
|
||||
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
|
||||
return encoder.Encode(playload, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Token负载
|
||||
/// </summary>
|
||||
/// <param name="token">登录Token</param>
|
||||
/// <returns>负载信息</returns>
|
||||
public LoginUserInfo GetPlayload(string token) {
|
||||
var key = Encoding.UTF8.GetBytes(this.Options.SecKey);
|
||||
var serializer = new JsonNetSerializer();
|
||||
var dtProvider = new UtcDateTimeProvider();
|
||||
var validator = new JwtValidator(serializer, dtProvider);
|
||||
var urlEncoder = new JwtBase64UrlEncoder();
|
||||
var algorithm = new HMACSHA256Algorithm();
|
||||
var decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
|
||||
return decoder.DecodeToObject<LoginUserInfo>(token, key, true);
|
||||
}
|
||||
}
|
||||
}
|
52
Falcon.SugarApi/JWT/JwtTokenBuilderExtend.cs
Normal file
52
Falcon.SugarApi/JWT/JwtTokenBuilderExtend.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// JwtTokenBuilder扩展
|
||||
/// </summary>
|
||||
public static class JwtTokenBuilderExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试获取Token
|
||||
/// </summary>
|
||||
/// <param name="builder">生成器</param>
|
||||
/// <param name="userInfo">用户信息</param>
|
||||
/// <param name="token">生成的token</param>
|
||||
/// <param name="exception">失败异常</param>
|
||||
/// <returns>成功True,失败False</returns>
|
||||
public static bool TryGetToken(this JwtTokenBuilder builder, LoginUserInfo userInfo, out string? token, out Exception? exception) {
|
||||
try {
|
||||
token = builder.GetToken(userInfo);
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
token = null;
|
||||
exception = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="builder">jwt创建器</param>
|
||||
/// <param name="token">token</param>
|
||||
/// <param name="userInfo">用户信息</param>
|
||||
/// <param name="exception">异常</param>
|
||||
/// <returns>True成功,False失败</returns>
|
||||
public static bool TryGetPlayload(this JwtTokenBuilder builder, string token, out LoginUserInfo? userInfo, out Exception? exception) {
|
||||
try {
|
||||
userInfo = builder.GetPlayload(token);
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
userInfo = null;
|
||||
exception = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
Falcon.SugarApi/JWT/LoginDto.cs
Normal file
17
Falcon.SugarApi/JWT/LoginDto.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录信息
|
||||
/// </summary>
|
||||
public class LoginDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
29
Falcon.SugarApi/JWT/LoginUserInfo.cs
Normal file
29
Falcon.SugarApi/JWT/LoginUserInfo.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// JWT载荷信息
|
||||
/// </summary>
|
||||
public class LoginUserInfo
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录用户名
|
||||
/// </summary>
|
||||
public string? UserName { get; set; }
|
||||
/// <summary>
|
||||
/// 用户角色
|
||||
/// </summary>
|
||||
public List<string> Roles { get; set; } = new List<string>();
|
||||
/// <summary>
|
||||
/// 其他扩展用户信息
|
||||
/// </summary>
|
||||
public Dictionary<string, object> UserInfoExtend { get; set; } = new Dictionary<string, object>();
|
||||
/// <summary>
|
||||
/// 登录时间
|
||||
/// </summary>
|
||||
public DateTime? LoginTime { get; set; }
|
||||
}
|
||||
}
|
21
Falcon.SugarApi/JWT/TokenDto.cs
Normal file
21
Falcon.SugarApi/JWT/TokenDto.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Falcon.SugarApi.JWT
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录凭据
|
||||
/// </summary>
|
||||
public class TokenDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录结果
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
/// <summary>
|
||||
/// 凭据
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
7
Falcon.SugarApi/JWT/readme.md
Normal file
7
Falcon.SugarApi/JWT/readme.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
初始化过程:
|
||||
1. 初始化相关表:
|
||||
2. 初始化用户系统:检查是否存在用户,如果没有用户插入默认管理员用户,后期可以删除。
|
||||
2. 初始化角色系统:
|
||||
> 1. 检查系统所有控制器,查询是否标注ApiExplorerSettings特性,如果标注查询是否定义GroupName,不为空则列为角色之一。
|
||||
> 2. 检查角色表,如果发现缺少某角色则插入。检查是否存在超管角色,如果不存在则插入
|
||||
|
Loading…
Reference in New Issue
Block a user