Seamus Gu 4 months ago
parent
commit
49b5f91bff

+ 2 - 2
ERP.Core/Service/AuthService.cs

@@ -83,7 +83,7 @@ namespace ERP.Core.Service
 
         private void CheckLogin(SecurityConfig securityConfig, string password, SysUser loginUser)
         {
-            var passwordErrorRedisKey = AuthConstant.PasswordError + IPUtil.GetClientIp();
+            var passwordErrorRedisKey = AuthConstant.PASSWORD_ERROR + IPUtil.GetClientIp();
             var errorNumber = RedisHelper.Get(passwordErrorRedisKey);
             var hasErrorRedis = !errorNumber.IsNullOrEmpty();
 
@@ -99,7 +99,7 @@ namespace ERP.Core.Service
             {
                 if (!hasErrorRedis)
                 {
-                    RedisHelper.SaveExpire(passwordErrorRedisKey, AuthConstant.FirstPasswordError, securityConfig.LockTime);
+                    RedisHelper.SaveExpire(passwordErrorRedisKey, AuthConstant.FIRST_PASSWORD_ERROR, securityConfig.LockTime);
                 }
                 else
                 {

+ 8 - 8
ERP.Framework/Constants/AuthConstant.cs

@@ -11,12 +11,12 @@ namespace ERP.Framework.Constants
         /// <summary>
         /// 登录用户密码错误
         /// </summary>
-        public const string PasswordError = "auth:password-error:";
+        public const string PASSWORD_ERROR = "auth:password-error:";
 
         /// <summary>
         /// 
         /// </summary>
-        public const string OnlineToken = "auth:online-token:";
+        public const string ONLINE_TOKEN = "auth:online-token:";
 
         /// <summary>
         /// 最后一次登录时间(时限为token 长久有效期),
@@ -24,31 +24,31 @@ namespace ERP.Framework.Constants
         /// -1 为被踢出
         /// -2 为被顶下线
         /// </summary>
-        public const string LastActivity = "auth:last-activity:";
+        public const string LAST_ACTIVITY = "auth:last-activity:";
 
         /// <summary>
         ///  用戶信息
         /// </summary>
-        public const string UserInfo = "auth:user-info:";
+        public const string USER_INFO = "auth:user-info:";
 
         /// <summary>
         /// Token List
         /// </summary>
-        public const string TokenList = "auth:token-list:";
+        public const string TOKEN_LIST = "auth:token-list:";
 
         /// <summary>
         /// 被踢下线
         /// </summary>
-        public const string KickedOff = "-1";
+        public const string KICKED_OFF = "-1";
 
         /// <summary>
         /// 被顶下线
         /// </summary>
-        public const string Substituted = "-2";
+        public const string SUBSTITUTED = "-2";
 
         /// <summary>
         /// 首次密码错误
         /// </summary>
-        public const string FirstPasswordError = "1";
+        public const string FIRST_PASSWORD_ERROR = "1";
     }
 }

+ 1 - 1
ERP.Framework/Constants/FrameworkConstant.cs

@@ -12,6 +12,7 @@ namespace ERP.Framework.Constants
         public const string ADMIN = "admin";
         public const string ADMIN_PERMISSION = "*:*:*";
         public const string SECURITY_CONFIG = "Security";
+        public const string TOKEN_PREFIX = "Bearer";
 
 
         //public const string AppSetting = "appsettings.json";
@@ -26,6 +27,5 @@ namespace ERP.Framework.Constants
         //public const string Logging = "Logging";
         //public const string Whites = "Whites";
         //public const string Logger = "Logger";
-        //public const string TokenPrefix = "Bearer";
     }
 }

+ 6 - 0
ERP.Framework/ERP.Framework.csproj

@@ -6,6 +6,11 @@
     <Nullable>enable</Nullable>
   </PropertyGroup>
 
+
+  <ItemGroup>
+    <FrameworkReference Include="Microsoft.AspNetCore.App" />
+  </ItemGroup>
+  
   <ItemGroup>
     <Folder Include="Office\" />
     <Folder Include="Validator\" />
@@ -26,6 +31,7 @@
     <PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
     <PackageReference Include="StackExchange.Redis" Version="2.6.122" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
+    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.3" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
   </ItemGroup>
 

+ 1 - 1
ERP.Core/Enum/DeviceEnum.cs → ERP.Framework/Enum/DeviceEnum.cs

@@ -1,6 +1,6 @@
 using System.ComponentModel;
 
-namespace ERP.Core.Emum
+namespace ERP.Framework.Emum
 {
     /// <summary>
     /// 设备类型枚举

+ 1 - 1
ERP.Core/Enum/StatusEnum.cs → ERP.Framework/Enum/StatusEnum.cs

@@ -5,7 +5,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace ERP.Core.Enum
+namespace ERP.Framework.Enum
 {
     /// <summary>
     /// 

+ 21 - 0
ERP.Framework/Security/Core/JwtToken.cs

@@ -0,0 +1,21 @@
+namespace ERP.Framework.Security.Core
+{
+    public class JwtToken
+    {
+        public JwtToken(string token, int expire)
+        {
+            Token = token;
+            Expire = expire;
+        }
+
+        /// <summary>
+        /// Token 字符串
+        /// </summary>
+        public string Token { get; set; }
+
+        /// <summary>
+        /// Tokne 所对应的Redis失效时间
+        /// </summary>
+        public int Expire { get; set; }
+    }
+}

+ 12 - 14
ERP.Core/Dto/LoginUser.cs → ERP.Framework/Security/Core/LoginUser.cs

@@ -1,13 +1,11 @@
-using ERP.Core.Entity;
-using ERP.Core.Enum;
-using Newtonsoft.Json;
+using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace ERP.Core.Dto
+namespace ERP.Framework.Security.Core
 {
     public class LoginUser
     {
@@ -61,15 +59,15 @@ namespace ERP.Core.Dto
         {
         }
 
-        public LoginUser(SysUser user, List<string> roleKeys, List<string> permission)
-        {
-            this.UserId = user.Id;
-            this.UserName=user.UserName;
-            this.RoleKeys = roleKeys;
-            this.Permission = permission;
-            this.Avatar = user.Avatar;
-            this.Email = user.Email;
-            this.UserStatus = user.UserStatus;
-        }
+        //public LoginUser(SysUser user, List<string> roleKeys, List<string> permission)
+        //{
+        //    this.UserId = user.Id;
+        //    this.UserName=user.UserName;
+        //    this.RoleKeys = roleKeys;
+        //    this.Permission = permission;
+        //    this.Avatar = user.Avatar;
+        //    this.Email = user.Email;
+        //    this.UserStatus = user.UserStatus;
+        //}
     }
 }

+ 11 - 0
ERP.Framework/Security/Core/TokenSign.cs

@@ -0,0 +1,11 @@
+using ERP.Framework.Emum;
+
+namespace ERP.Framework.Security.Core
+{
+    public class TokenSign
+    {
+        public string TokenId { get; set; } = string.Empty;
+
+        public DeviceEnum Device { get; set; }
+    }
+}

+ 162 - 0
ERP.Framework/Security/LoginHelper.cs

@@ -0,0 +1,162 @@
+// <author></author>
+// <date></date>
+// <description></description>
+
+using ERP.Framework.Cache;
+using ERP.Framework.Config;
+using ERP.Framework.Constants;
+using ERP.Framework.Emum;
+using ERP.Framework.Security.Core;
+using ERP.Framework.Utils;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ERP.Framework.Security
+{
+    public class LoginHelper
+    {
+        /// <summary>
+        /// 根据设备进行登录
+        /// </summary>
+        /// <param name="userInfo"></param>
+        /// <param name="securityConfig"></param>
+        /// <param name="device"></param>
+        /// <param name="deviceKey"></param>
+        /// <returns></returns>
+        public static JwtToken LoginByDevice(
+            LoginUser userInfo
+            , SecurityConfig securityConfig
+            , DeviceEnum device
+            , string deviceKey)
+        {
+            string tokenId;
+
+            var tokenList = GetTokenList(userInfo.UserId);
+
+            var token = TokenHelper.CreateToken(
+                securityConfig.JwtSecurityKey
+                , userInfo.UserId
+                , userInfo.UserName
+                , securityConfig.TimeOut
+                , out tokenId);
+
+
+            //Todo 不允许并发登录处理
+            //Todo 处理超出的会话
+
+            tokenList.Add(new TokenSign
+            {
+                TokenId = tokenId,
+                Device = device
+            });
+
+            SetTokenList(userInfo.UserId, tokenList);
+
+            SetLastActivity(tokenId, securityConfig.ActivityTimeOut, securityConfig.TimeOut);
+
+            SetUserInfo(tokenId, userInfo, securityConfig.TimeOut);
+
+            var result = new JwtToken(FrameworkConstant.TOKEN_PREFIX + " " + token, securityConfig.TimeOut);
+
+            return result;
+        }
+
+        //public static LoginUser GetLoginUser()
+        //{
+        //    var httpContextAccessor = new HttpContextAccessor();
+        //    var httpContext = httpContextAccessor.HttpContext;
+        //    var loginUser = httpContext!.Items["LoginUser"] as LoginUser;
+
+        //    if (loginUser != null)
+        //    {
+        //        return loginUser;
+        //    }
+
+        //    var tokenId = httpContext!.Items["TokenId"] as string;
+        //    var redisData = RedisHelper.Get(AuthConstant.UserInfo + tokenId);
+        //    var result = JsonConvert.DeserializeObject<LoginUser>(redisData);
+        //    httpContext.Items["LoginUser"] = result;
+        //    return result;
+        //}
+
+        /// <summary>
+        /// 获取TokenList缓存
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <returns></returns>
+        private static List<TokenSign> GetTokenList(long userId)
+        {
+            var tokenListData = RedisHelper.Get(AuthConstant.TOKEN_LIST + CryptoUtil.MD5(userId.ToString()));
+
+            if (!tokenListData.IsNullOrEmpty())
+            {
+                var data = JsonConvert.DeserializeObject<List<TokenSign>>(tokenListData);
+                return data;`
+            }
+
+            return new List<TokenSign>();
+        }
+
+        /// <summary>
+        /// 创建Last Activity
+        /// </summary>
+        /// <param name="tokenId"></param>
+        /// <param name="activityTimeOut"></param>
+        private static void SetLastActivity(
+            string tokenId
+            , int activityTimeOut
+            , int tokenTimeOut)
+        {
+            var key = AuthConstant.LAST_ACTIVITY + tokenId;
+            var val = DateTime.Now.AddMinutes(activityTimeOut).ToDateLongString();
+            RedisHelper.SaveExpire(key, val, tokenTimeOut);
+        }
+
+        /// <summary>
+        /// 退出用户
+        /// </summary>
+        /// <param name="tokenId"></param>
+        /// <param name="activityTimeOut"></param>
+        private static void LogoutUser(
+          string tokenId
+          , int tokenTimeOut)
+        {
+            var key = AuthConstant.LAST_ACTIVITY + tokenId;
+            RedisHelper.SaveExpire(key, AuthConstant.SUBSTITUTED, tokenTimeOut);
+        }
+
+        /// <summary>
+        /// 创建Token List缓存
+        /// </summary>
+        /// <param name="tokenId"></param>
+        /// <param name="activityTimeOut"></param>
+        private static void SetTokenList(
+            long userId
+            , List<TokenSign> list)
+        {
+            var key = AuthConstant.TOKEN_LIST + CryptoUtil.MD5(userId.ToString());
+            var val = JsonConvert.SerializeObject(list);
+            RedisHelper.Save(key, val);
+        }
+
+        /// <summary>
+        /// 创建UserInfo
+        /// </summary>
+        /// <param name="tokenId"></param>
+        /// <param name="activityTimeOut"></param>
+        private static void SetUserInfo(
+            string tokenId
+            , LoginUser loginUser
+            , int tokenTimeOut)
+        {
+            var key = AuthConstant.USER_INFO + tokenId;
+            var val = JsonConvert.SerializeObject(loginUser);
+            RedisHelper.SaveExpire(key, val, tokenTimeOut);
+        }
+    }
+}

+ 82 - 0
ERP.Framework/Security/TokenHelper.cs

@@ -0,0 +1,82 @@
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+
+namespace ERP.Framework.Security
+{ 
+    public static class TokenHelper
+    {
+        /// <summary>
+        /// 创建Token
+        /// </summary>
+        /// <param name="securityKey">密匙</param>
+        /// <param name="userId">用户Id</param>
+        /// <param name="expires">Token 过期时间</param>
+        /// <param name="tokenId">tokenId</param>
+        /// <returns></returns>
+        public static string CreateToken(
+            string securityKey
+            , long userId
+            , string userName
+            , int expires
+            , out string tokenId)
+        {
+            tokenId = Guid.NewGuid().ToString();
+
+            var claims = new Claim[]
+            {
+                new Claim(JwtRegisteredClaimNames.NameId,userId.ToString()),
+                new Claim(ClaimTypes.Name,userName)
+            };
+
+            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
+
+            var token = new JwtSecurityToken(
+                issuer: "Erp",
+                audience: "ERP",
+                claims: claims,
+                notBefore: DateTime.Now,
+                expires: DateTime.Now.AddMinutes(expires),
+                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
+            );
+
+            return new JwtSecurityTokenHandler().WriteToken(token);
+        }
+
+        public static bool ValidateToken(
+            string token,
+            string secutityKey)
+        {
+            var tokenHandler = new JwtSecurityTokenHandler();
+            var validationParameters = new TokenValidationParameters
+            {
+                ValidateIssuer = true, //是否验证Issuer
+                ValidateAudience = true, //是否验证Audience
+                ValidateLifetime = true, //是否验证失效时间---默认添加300s后过期
+                ValidateIssuerSigningKey = true, //是否验证SecurityKey
+                ClockSkew = TimeSpan.Zero,
+                ValidIssuer = "ERP",
+                ValidAudience = "ERP",
+                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secutityKey)),
+            };
+
+            try
+            {
+                tokenHandler.ValidateToken(token, validationParameters, out _);
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        public static IEnumerable<Claim> GetClaims(string token)
+        {
+            var tokenHandler = new JwtSecurityTokenHandler();
+            var jwtToken = tokenHandler.ReadJwtToken(token);
+            return jwtToken.Claims;
+        }
+    }
+}