diff --git a/Falcon.SugarApi.Test/JwtTokenBuilderTest.cs b/Falcon.SugarApi.Test/JwtTokenBuilderTest.cs
new file mode 100644
index 0000000..6a69a56
--- /dev/null
+++ b/Falcon.SugarApi.Test/JwtTokenBuilderTest.cs
@@ -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
+{
+ ///
+ /// JwtTokenBuilderTest
+ ///
+ [TestClass]
+ public class JwtTokenBuilderTest
+ {
+ ///
+ /// Token获取测试
+ ///
+ [TestMethod]
+ public void GetTokenTest() {
+ var loginTime = DateTime.Now;
+ var playload = new LoginUserInfo {
+ UserName = "abdc",
+ LoginTime = loginTime,
+ Roles = new List { "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"));
+ }
+ }
+}
diff --git a/Falcon.SugarApi/Encryption/AESConfig.cs b/Falcon.SugarApi/Encryption/AESConfig.cs
index 1e9627b..d2bdc8a 100644
--- a/Falcon.SugarApi/Encryption/AESConfig.cs
+++ b/Falcon.SugarApi/Encryption/AESConfig.cs
@@ -1,10 +1,15 @@
-namespace Falcon.SugarApi.Encryption
+using Microsoft.Extensions.Options;
+
+namespace Falcon.SugarApi.Encryption
{
///
/// AES加密算法配置
///
- public class AESConfig
+ public class AESConfig : IOptions
{
+ ///
+ public AESConfig Value => this;
+
private int keyLength = 32;
///
@@ -16,5 +21,6 @@
/// 秘钥字符表
///
public string KeyChars { get; set; } = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789,.!/*\";
+
}
}
diff --git a/Falcon.SugarApi/Falcon.SugarApi.csproj b/Falcon.SugarApi/Falcon.SugarApi.csproj
index 9049b42..76bb987 100644
--- a/Falcon.SugarApi/Falcon.SugarApi.csproj
+++ b/Falcon.SugarApi/Falcon.SugarApi.csproj
@@ -12,6 +12,7 @@
+
diff --git a/Falcon.SugarApi/JWT/AESTokenbuilder.cs b/Falcon.SugarApi/JWT/AESTokenbuilder.cs
new file mode 100644
index 0000000..141f44b
--- /dev/null
+++ b/Falcon.SugarApi/JWT/AESTokenbuilder.cs
@@ -0,0 +1,42 @@
+using Falcon.SugarApi.Encryption;
+
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// 使用AES算法生成Token
+ ///
+ public class AESTokenbuilder : IJwtTokenBuilder
+ {
+ ///
+ /// AES加密算法
+ ///
+ public IAESEncryption AES { get; set; }
+ ///
+ /// jwt生成参数
+ ///
+ public JwtContext Jwt { get; }
+
+ ///
+ /// 构造AES的Token
+ ///
+ /// AES参数
+ /// jwt生成参数
+ public AESTokenbuilder(AESConfig config, JwtContext jwt) {
+ Jwt = jwt;
+ AES = new AESProvider(config);
+ }
+
+ /// /
+ public LoginUserInfo GetPlayload(string token) {
+ token.ThrowNullExceptionWhenNull();
+ return this.AES.Decrypt(Jwt.SecKey, token);
+ }
+
+ /// /
+ public string GetToken(LoginUserInfo playload) {
+ playload.ThrowNullExceptionWhenNull();
+ return this.AES.Encrypt(Jwt.SecKey, playload);
+ }
+ }
+
+}
diff --git a/Falcon.SugarApi/JWT/ApiAuthorizationAttribute.cs b/Falcon.SugarApi/JWT/ApiAuthorizationAttribute.cs
new file mode 100644
index 0000000..2f0639e
--- /dev/null
+++ b/Falcon.SugarApi/JWT/ApiAuthorizationAttribute.cs
@@ -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
+{
+ ///
+ /// 验证
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ public class ApiAuthorizationAttribute : Attribute, IAuthorizationFilter
+ {
+ ///
+ /// 用户需要具有的角色
+ ///
+ public List Roles { get; set; } = new List();
+
+ ///
+ public void OnAuthorization(AuthorizationFilterContext context) {
+ if (context.Filters.Any(f => f is IAllowAnonymousFilter)) {
+ return;
+ }
+ var option = context.HttpContext.RequestServices.GetRequiredService();
+ 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;
+ }
+
+ ///
+ /// 返回授权失败
+ ///
+ /// 上下文
+ private static void Unauthorized(AuthorizationFilterContext context) {
+ context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/IJwtTokenBuilder.cs b/Falcon.SugarApi/JWT/IJwtTokenBuilder.cs
new file mode 100644
index 0000000..19ba5e5
--- /dev/null
+++ b/Falcon.SugarApi/JWT/IJwtTokenBuilder.cs
@@ -0,0 +1,22 @@
+namespace Falcon.SugarApi.JWT
+{
+
+ ///
+ /// jwt token 生成器
+ ///
+ public interface IJwtTokenBuilder
+ {
+ ///
+ /// 获取Token负载
+ ///
+ /// 登录Token
+ /// 负载信息
+ LoginUserInfo GetPlayload(string token);
+ ///
+ /// 获取Token
+ ///
+ /// 负载
+ /// Token对象
+ string GetToken(LoginUserInfo playload);
+ }
+}
\ No newline at end of file
diff --git a/Falcon.SugarApi/JWT/IServiceCollectionExtend.cs b/Falcon.SugarApi/JWT/IServiceCollectionExtend.cs
new file mode 100644
index 0000000..a598a30
--- /dev/null
+++ b/Falcon.SugarApi/JWT/IServiceCollectionExtend.cs
@@ -0,0 +1,56 @@
+using Falcon.SugarApi.Encryption;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// 服务扩展
+ ///
+ public static class IServiceCollectionExtend
+ {
+ ///
+ /// 注册Falcon.SqlSugar.JWT
+ ///
+ /// 服务集合
+ /// 服务集合
+ public static IServiceCollection AddFalconJWT(this IServiceCollection services)
+ => services.AddFalconJWT(null);
+
+ ///
+ /// 注册Falcon.SqlSugar.JWT.JwtContext,并配置相关参数
+ /// 消费端通过JwtContext注入
+ ///
+ /// 服务集合
+ /// 参数创建器
+ /// 服务集合
+ public static IServiceCollection AddFalconJWT(this IServiceCollection services, Action? OptionBuilder) {
+ services.AddSingleton(sp => {
+ var option = new JwtContext();
+ var jtb = sp.GetService();
+ if (jtb == null) {
+ var aesc = sp.GetService() ?? new AESConfig();
+ jtb = new AESTokenbuilder(aesc, option);
+ }
+ option.JwtTokenBuilder = jtb;
+
+ var ulj = sp.GetService();
+ 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;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/IUserLogin.cs b/Falcon.SugarApi/JWT/IUserLogin.cs
new file mode 100644
index 0000000..c335415
--- /dev/null
+++ b/Falcon.SugarApi/JWT/IUserLogin.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// 用户登录验证接口
+ ///
+ public interface IUserLogin
+ {
+ ///
+ /// 登录是否成功
+ ///
+ /// 登录凭据
+ /// 成功返回用户信息,否则返回null
+ LoginUserInfo? Login(LoginDto login);
+
+ ///
+ /// 用户登出
+ ///
+ /// 用户名
+ /// True成功,False失败
+ bool LogOut(LoginUserInfo userInfo);
+
+ ///
+ /// 登录用户验证。检查客户端提供的已登录用户是否仍然处于登录中。
+ ///
+ /// 登录凭据
+ /// 登录中返回True,否则返回False
+ bool CheckUserLogin(LoginUserInfo userInfo);
+
+ ///
+ /// 用户是否具有某个角色
+ ///
+ /// 用户信息
+ /// 需要具有的角色组
+ /// True具有,False不具有
+ bool UserInRoles(LoginUserInfo userInfo, List roles);
+ }
+}
diff --git a/Falcon.SugarApi/JWT/JwtContext.cs b/Falcon.SugarApi/JWT/JwtContext.cs
new file mode 100644
index 0000000..1e8a1c0
--- /dev/null
+++ b/Falcon.SugarApi/JWT/JwtContext.cs
@@ -0,0 +1,29 @@
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// JWT认证上下文
+ ///
+ public class JwtContext
+ {
+ ///
+ /// 验证头的键值
+ ///
+ public string AuthHeaderKey { get; set; } = "auth";
+
+ ///
+ /// 用户Token加密的秘钥
+ ///
+ public string SecKey { get; set; } = "fefafwefwf464664f64e64f63";
+
+ ///
+ /// jwtToken生成器
+ ///
+ public IJwtTokenBuilder? JwtTokenBuilder { get; set; }
+
+ ///
+ /// 用户登录管理
+ ///
+ public IUserLogin? UserLogin { get; set; }
+
+ }
+}
diff --git a/Falcon.SugarApi/JWT/JwtTokenBuilder.cs b/Falcon.SugarApi/JWT/JwtTokenBuilder.cs
new file mode 100644
index 0000000..8016c48
--- /dev/null
+++ b/Falcon.SugarApi/JWT/JwtTokenBuilder.cs
@@ -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
+{
+ ///
+ /// jwt token 生成器
+ ///
+ public class JwtTokenBuilder : IJwtTokenBuilder
+ {
+ ///
+ /// 使用默认配置实例化
+ ///
+ public JwtTokenBuilder() : this(new JwtContext()) { }
+
+ ///
+ /// 实例化
+ ///
+ /// JWT认证配置
+ public JwtTokenBuilder(JwtContext options) {
+ Options = options;
+ }
+
+ ///
+ /// JWT认证配置
+ ///
+ public JwtContext Options { get; }
+
+ ///
+ /// 获取Token
+ ///
+ /// 负载
+ /// Token对象
+ 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);
+ }
+
+ ///
+ /// 获取Token负载
+ ///
+ /// 登录Token
+ /// 负载信息
+ 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(token, key, true);
+ }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/JwtTokenBuilderExtend.cs b/Falcon.SugarApi/JWT/JwtTokenBuilderExtend.cs
new file mode 100644
index 0000000..b16b5da
--- /dev/null
+++ b/Falcon.SugarApi/JWT/JwtTokenBuilderExtend.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// JwtTokenBuilder扩展
+ ///
+ public static class JwtTokenBuilderExtend
+ {
+ ///
+ /// 尝试获取Token
+ ///
+ /// 生成器
+ /// 用户信息
+ /// 生成的token
+ /// 失败异常
+ /// 成功True,失败False
+ 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;
+ }
+ }
+
+ ///
+ /// 尝试获取用户信息
+ ///
+ /// jwt创建器
+ /// token
+ /// 用户信息
+ /// 异常
+ /// True成功,False失败
+ 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;
+ }
+ }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/LoginDto.cs b/Falcon.SugarApi/JWT/LoginDto.cs
new file mode 100644
index 0000000..4a7d67e
--- /dev/null
+++ b/Falcon.SugarApi/JWT/LoginDto.cs
@@ -0,0 +1,17 @@
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// 登录信息
+ ///
+ public class LoginDto
+ {
+ ///
+ /// 用户名
+ ///
+ public string UserName { get; set; }
+ ///
+ /// 密码
+ ///
+ public string Password { get; set; }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/LoginUserInfo.cs b/Falcon.SugarApi/JWT/LoginUserInfo.cs
new file mode 100644
index 0000000..9ea2c65
--- /dev/null
+++ b/Falcon.SugarApi/JWT/LoginUserInfo.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// JWT载荷信息
+ ///
+ public class LoginUserInfo
+
+ {
+ ///
+ /// 登录用户名
+ ///
+ public string? UserName { get; set; }
+ ///
+ /// 用户角色
+ ///
+ public List Roles { get; set; } = new List();
+ ///
+ /// 其他扩展用户信息
+ ///
+ public Dictionary UserInfoExtend { get; set; } = new Dictionary();
+ ///
+ /// 登录时间
+ ///
+ public DateTime? LoginTime { get; set; }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/TokenDto.cs b/Falcon.SugarApi/JWT/TokenDto.cs
new file mode 100644
index 0000000..0871d21
--- /dev/null
+++ b/Falcon.SugarApi/JWT/TokenDto.cs
@@ -0,0 +1,21 @@
+namespace Falcon.SugarApi.JWT
+{
+ ///
+ /// 登录凭据
+ ///
+ public class TokenDto
+ {
+ ///
+ /// 登录结果
+ ///
+ public bool Success { get; set; }
+ ///
+ /// 信息
+ ///
+ public string Message { get; set; }
+ ///
+ /// 凭据
+ ///
+ public string Token { get; set; }
+ }
+}
diff --git a/Falcon.SugarApi/JWT/readme.md b/Falcon.SugarApi/JWT/readme.md
new file mode 100644
index 0000000..ecc3437
--- /dev/null
+++ b/Falcon.SugarApi/JWT/readme.md
@@ -0,0 +1,7 @@
+初始化过程:
+1. 初始化相关表:
+2. 初始化用户系统:检查是否存在用户,如果没有用户插入默认管理员用户,后期可以删除。
+2. 初始化角色系统:
+> 1. 检查系统所有控制器,查询是否标注ApiExplorerSettings特性,如果标注查询是否定义GroupName,不为空则列为角色之一。
+> 2. 检查角色表,如果发现缺少某角色则插入。检查是否存在超管角色,如果不存在则插入
+