diff --git a/Infrastructure/AppSetting.cs b/Infrastructure/AppSetting.cs
index fdfefa22..96cea888 100644
--- a/Infrastructure/AppSetting.cs
+++ b/Infrastructure/AppSetting.cs
@@ -42,6 +42,16 @@ namespace Infrastructure
///
public string RedisConf { get; set; }
+ ///
+ /// JWT签名密钥,用于本地认证模式下生成和验证JWT Token
+ ///
+ public string JwtSecret { get; set; } = "openauth_default_jwt_secret_key_2024";
+
+ ///
+ /// JWT Token过期天数,默认10天
+ ///
+ public int JwtExpireDays { get; set; } = 10;
+
//是否是Identity授权方式
public bool IsIdentityAuth => !string.IsNullOrEmpty(IdentityServerUrl);
}
diff --git a/OpenAuth.App/SSO/JwtTokenHelper.cs b/OpenAuth.App/SSO/JwtTokenHelper.cs
new file mode 100644
index 00000000..c700314b
--- /dev/null
+++ b/OpenAuth.App/SSO/JwtTokenHelper.cs
@@ -0,0 +1,144 @@
+using System;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using Microsoft.IdentityModel.Tokens;
+
+namespace OpenAuth.App.SSO
+{
+ ///
+ /// JWT Token辅助类,用于本地认证模式下的Token生成和验证
+ ///
+ public static class JwtTokenHelper
+ {
+ ///
+ /// 生成JWT Token
+ ///
+ /// 用户账号
+ /// 用户名称
+ /// 应用标识
+ /// 会话ID,用作缓存Key
+ /// JWT签名密钥
+ /// 过期天数
+ /// JWT Token字符串
+ public static string GenerateToken(string account, string name, string appKey, string sessionId, string secret, int expireDays = 10)
+ {
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(EnsureKeyLength(secret)));
+ var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+
+ var claims = new[]
+ {
+ new Claim(JwtRegisteredClaimNames.Sub, account),
+ new Claim(JwtRegisteredClaimNames.Jti, sessionId),
+ new Claim("name", name ?? string.Empty),
+ new Claim("app_key", appKey ?? string.Empty),
+ };
+
+ var token = new JwtSecurityToken(
+ issuer: "OpenAuth",
+ audience: "OpenAuth",
+ claims: claims,
+ expires: DateTime.UtcNow.AddDays(expireDays),
+ signingCredentials: credentials
+ );
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+
+ ///
+ /// 验证JWT Token并返回ClaimsPrincipal
+ ///
+ /// JWT Token字符串
+ /// JWT签名密钥
+ /// 验证成功返回ClaimsPrincipal,失败返回null
+ public static ClaimsPrincipal ValidateToken(string token, string secret)
+ {
+ if (string.IsNullOrEmpty(token))
+ return null;
+
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(EnsureKeyLength(secret)));
+
+ try
+ {
+ var validationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = key,
+ ValidateIssuer = true,
+ ValidIssuer = "OpenAuth",
+ ValidateAudience = true,
+ ValidAudience = "OpenAuth",
+ ValidateLifetime = true,
+ ClockSkew = TimeSpan.FromMinutes(5)
+ };
+
+ var principal = tokenHandler.ValidateToken(token, validationParameters, out _);
+ return principal;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// 从JWT Token中提取会话ID(jti)
+ ///
+ public static string GetSessionId(string token)
+ {
+ var principal = ReadTokenWithoutValidation(token);
+ return principal?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
+ }
+
+ ///
+ /// 从JWT Token中提取用户账号(sub)
+ ///
+ public static string GetAccount(string token)
+ {
+ var principal = ReadTokenWithoutValidation(token);
+ return principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
+ ?? principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+ }
+
+ ///
+ /// 不验证签名,仅读取Token中的Claims(用于快速提取信息)
+ ///
+ private static ClaimsPrincipal ReadTokenWithoutValidation(string token)
+ {
+ if (string.IsNullOrEmpty(token))
+ return null;
+
+ try
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ if (!tokenHandler.CanReadToken(token))
+ return null;
+
+ var jwtToken = tokenHandler.ReadJwtToken(token);
+ var identity = new ClaimsIdentity(jwtToken.Claims);
+ return new ClaimsPrincipal(identity);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// 确保密钥长度至少为32字节(256位),不足时进行填充
+ ///
+ private static string EnsureKeyLength(string secret)
+ {
+ if (string.IsNullOrEmpty(secret))
+ secret = "openauth_default_jwt_secret_key_2024";
+
+ // HMAC-SHA256要求密钥至少128位(16字节),建议256位(32字节)
+ while (secret.Length < 32)
+ {
+ secret += secret;
+ }
+ return secret.Substring(0, Math.Max(32, secret.Length));
+ }
+ }
+}
diff --git a/OpenAuth.App/SSO/LocalAuth.cs b/OpenAuth.App/SSO/LocalAuth.cs
index fb604ef5..f22d4c5e 100644
--- a/OpenAuth.App/SSO/LocalAuth.cs
+++ b/OpenAuth.App/SSO/LocalAuth.cs
@@ -36,6 +36,7 @@ namespace OpenAuth.App.SSO
///
/// 如果是Identity,则返回信息为用户账号
+ /// 如果是本地认证,则返回JWT Token字符串
///
///
private string GetToken()
@@ -54,6 +55,15 @@ namespace OpenAuth.App.SSO
return cookie ?? String.Empty;
}
+ ///
+ /// 从JWT Token中提取会话ID(jti),用于缓存查找
+ ///
+ private string GetSessionIdFromToken(string token)
+ {
+ if (string.IsNullOrEmpty(token)) return null;
+ return JwtTokenHelper.GetSessionId(token);
+ }
+
public bool CheckLogin(string token = "", string otherInfo = "")
{
if (_appConfiguration.Value.IsIdentityAuth)
@@ -73,7 +83,21 @@ namespace OpenAuth.App.SSO
try
{
- var result = _cacheContext.Get(token) != null;
+ // 验证JWT Token签名和有效期
+ var principal = JwtTokenHelper.ValidateToken(token, _appConfiguration.Value.JwtSecret);
+ if (principal == null)
+ {
+ return false;
+ }
+
+ // 检查会话是否在缓存中(确保未被登出失效)
+ var sessionId = GetSessionIdFromToken(token);
+ if (string.IsNullOrEmpty(sessionId))
+ {
+ return false;
+ }
+
+ var result = _cacheContext.Get(sessionId) != null;
return result;
}
catch (Exception ex)
@@ -95,17 +119,29 @@ namespace OpenAuth.App.SSO
return _app.GetAuthStrategyContext(GetToken());
}
AuthStrategyContext context = null;
- var user = _cacheContext.Get(GetToken());
- if (user != null)
+ var token = GetToken();
+
+ // 从JWT Token中直接提取用户账号
+ var account = JwtTokenHelper.GetAccount(token);
+ if (!string.IsNullOrEmpty(account))
{
- context = _app.GetAuthStrategyContext(user.Account);
+ // 验证会话是否有效(未被登出)
+ var sessionId = GetSessionIdFromToken(token);
+ if (!string.IsNullOrEmpty(sessionId))
+ {
+ var session = _cacheContext.Get(sessionId);
+ if (session != null)
+ {
+ context = _app.GetAuthStrategyContext(account);
+ }
+ }
}
return context;
}
///
/// 获取当前登录的用户名
- /// 通过URL中的Token参数或Cookie中的Token
+ /// 通过JWT Token中的claims直接提取用户账号
///
/// The account.
/// System.String.
@@ -116,10 +152,21 @@ namespace OpenAuth.App.SSO
return _httpContextAccessor.HttpContext.User.Identity.Name;
}
- var user = _cacheContext.Get(GetToken());
- if (user != null)
+ var token = GetToken();
+ // 从JWT Token中提取用户账号
+ var account = JwtTokenHelper.GetAccount(token);
+ if (!string.IsNullOrEmpty(account))
{
- return user.Account;
+ // 验证会话是否有效
+ var sessionId = GetSessionIdFromToken(token);
+ if (!string.IsNullOrEmpty(sessionId))
+ {
+ var session = _cacheContext.Get(sessionId);
+ if (session != null)
+ {
+ return account;
+ }
+ }
}
return "";
@@ -173,7 +220,12 @@ namespace OpenAuth.App.SSO
try
{
- _cacheContext.Remove(token);
+ // 从JWT Token中提取会话ID,删除缓存
+ var sessionId = GetSessionIdFromToken(token);
+ if (!string.IsNullOrEmpty(sessionId))
+ {
+ _cacheContext.Remove(sessionId);
+ }
return true;
}
catch
diff --git a/OpenAuth.App/SSO/LoginParse.cs b/OpenAuth.App/SSO/LoginParse.cs
index 9ef93a92..8f3084bb 100644
--- a/OpenAuth.App/SSO/LoginParse.cs
+++ b/OpenAuth.App/SSO/LoginParse.cs
@@ -13,6 +13,7 @@ using SqlSugar;
using Infrastructure.Extensions.AutofacManager;
using Infrastructure.Utilities;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
namespace OpenAuth.App.SSO
@@ -24,11 +25,13 @@ namespace OpenAuth.App.SSO
protected ISqlSugarClient SugarClient;
private ICacheContext _cacheContext;
private AppManager _appInfoService;
+ private IOptions _appConfiguration;
- public LoginParse( AppManager infoService, ICacheContext cacheContext, ISqlSugarClient client)
+ public LoginParse( AppManager infoService, ICacheContext cacheContext, ISqlSugarClient client, IOptions appConfiguration)
{
_appInfoService = infoService;
_cacheContext = cacheContext;
+ _appConfiguration = appConfiguration;
string tenantId = Define.DEFAULT_TENANT_ID;
var httpContextAccessor = AutofacContainerModule.GetService();
if (httpContextAccessor != null)
@@ -87,21 +90,33 @@ namespace OpenAuth.App.SSO
throw new Exception("账号状态异常,可能已停用");
}
+ // 生成唯一的会话ID作为缓存Key
+ var sessionId = Guid.NewGuid().ToString("N");
+ var expireDays = _appConfiguration.Value.JwtExpireDays;
+
var currentSession = new UserAuthSession
{
Account = model.Account,
Name = sysUserInfo.Name,
- Token = Guid.NewGuid().ToString().GetHashCode().ToString("x"),
+ Token = sessionId,
AppKey = model.AppKey,
CreateTime = TimeHelper.Now
- // , IpAddress = HttpContext.Current.Request.UserHostAddress
};
- //创建Session
- _cacheContext.Set(currentSession.Token, currentSession, TimeHelper.Now.AddDays(10));
+ //创建Session缓存,用于登出时失效判定
+ _cacheContext.Set(sessionId, currentSession, TimeHelper.Now.AddDays(expireDays));
+
+ // 生成JWT Token
+ var jwtToken = JwtTokenHelper.GenerateToken(
+ model.Account,
+ sysUserInfo.Name,
+ model.AppKey,
+ sessionId,
+ _appConfiguration.Value.JwtSecret,
+ expireDays);
result.Code = 200;
- result.Token = currentSession.Token;
+ result.Token = jwtToken;
}
catch (Exception ex)
{
diff --git a/OpenAuth.WebApi/appsettings.Production.json b/OpenAuth.WebApi/appsettings.Production.json
index 92646a9f..7490d247 100644
--- a/OpenAuth.WebApi/appsettings.Production.json
+++ b/OpenAuth.WebApi/appsettings.Production.json
@@ -19,6 +19,8 @@
},
"UploadPath": "", //附件上传的路径,如果为空则保存在站点根目录
"RedisConf": "your_redis_server:6379,password=your_redis_password", //redis配置信息
- "HttpHost": "http://*:52789" //启动绑定地址及端口
+ "HttpHost": "http://*:52789", //启动绑定地址及端口
+ "JwtSecret": "openauth_jwt_secret_key_please_change_in_production", //JWT签名密钥,生产环境请修改
+ "JwtExpireDays": 10 //JWT Token过期天数
}
}