🐛fix: #IJX34Q 非Identity的token支持jwt token

This commit is contained in:
yubaolee
2026-07-06 21:14:28 +08:00
parent 94f6f9e831
commit 912e1146d6
5 changed files with 239 additions and 16 deletions

View File

@@ -42,6 +42,16 @@ namespace Infrastructure
/// </summary> /// </summary>
public string RedisConf { get; set; } public string RedisConf { get; set; }
/// <summary>
/// JWT签名密钥用于本地认证模式下生成和验证JWT Token
/// </summary>
public string JwtSecret { get; set; } = "openauth_default_jwt_secret_key_2024";
/// <summary>
/// JWT Token过期天数默认10天
/// </summary>
public int JwtExpireDays { get; set; } = 10;
//是否是Identity授权方式 //是否是Identity授权方式
public bool IsIdentityAuth => !string.IsNullOrEmpty(IdentityServerUrl); public bool IsIdentityAuth => !string.IsNullOrEmpty(IdentityServerUrl);
} }

View File

@@ -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
{
/// <summary>
/// JWT Token辅助类用于本地认证模式下的Token生成和验证
/// </summary>
public static class JwtTokenHelper
{
/// <summary>
/// 生成JWT Token
/// </summary>
/// <param name="account">用户账号</param>
/// <param name="name">用户名称</param>
/// <param name="appKey">应用标识</param>
/// <param name="sessionId">会话ID用作缓存Key</param>
/// <param name="secret">JWT签名密钥</param>
/// <param name="expireDays">过期天数</param>
/// <returns>JWT Token字符串</returns>
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);
}
/// <summary>
/// 验证JWT Token并返回ClaimsPrincipal
/// </summary>
/// <param name="token">JWT Token字符串</param>
/// <param name="secret">JWT签名密钥</param>
/// <returns>验证成功返回ClaimsPrincipal失败返回null</returns>
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;
}
}
/// <summary>
/// 从JWT Token中提取会话IDjti
/// </summary>
public static string GetSessionId(string token)
{
var principal = ReadTokenWithoutValidation(token);
return principal?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
}
/// <summary>
/// 从JWT Token中提取用户账号sub
/// </summary>
public static string GetAccount(string token)
{
var principal = ReadTokenWithoutValidation(token);
return principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
?? principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
/// <summary>
/// 不验证签名仅读取Token中的Claims用于快速提取信息
/// </summary>
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;
}
}
/// <summary>
/// 确保密钥长度至少为32字节256位不足时进行填充
/// </summary>
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));
}
}
}

View File

@@ -36,6 +36,7 @@ namespace OpenAuth.App.SSO
/// <summary> /// <summary>
/// 如果是Identity则返回信息为用户账号 /// 如果是Identity则返回信息为用户账号
/// 如果是本地认证则返回JWT Token字符串
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private string GetToken() private string GetToken()
@@ -54,6 +55,15 @@ namespace OpenAuth.App.SSO
return cookie ?? String.Empty; return cookie ?? String.Empty;
} }
/// <summary>
/// 从JWT Token中提取会话IDjti用于缓存查找
/// </summary>
private string GetSessionIdFromToken(string token)
{
if (string.IsNullOrEmpty(token)) return null;
return JwtTokenHelper.GetSessionId(token);
}
public bool CheckLogin(string token = "", string otherInfo = "") public bool CheckLogin(string token = "", string otherInfo = "")
{ {
if (_appConfiguration.Value.IsIdentityAuth) if (_appConfiguration.Value.IsIdentityAuth)
@@ -73,7 +83,21 @@ namespace OpenAuth.App.SSO
try try
{ {
var result = _cacheContext.Get<UserAuthSession>(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<UserAuthSession>(sessionId) != null;
return result; return result;
} }
catch (Exception ex) catch (Exception ex)
@@ -95,17 +119,29 @@ namespace OpenAuth.App.SSO
return _app.GetAuthStrategyContext(GetToken()); return _app.GetAuthStrategyContext(GetToken());
} }
AuthStrategyContext context = null; AuthStrategyContext context = null;
var user = _cacheContext.Get<UserAuthSession>(GetToken()); var token = GetToken();
if (user != null)
// 从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<UserAuthSession>(sessionId);
if (session != null)
{
context = _app.GetAuthStrategyContext(account);
}
}
} }
return context; return context;
} }
/// <summary> /// <summary>
/// 获取当前登录的用户名 /// 获取当前登录的用户名
/// <para>通过URL中的Token参数或Cookie中的Token</para> /// <para>通过JWT Token中的claims直接提取用户账号</para>
/// </summary> /// </summary>
/// <param name="otherInfo">The account.</param> /// <param name="otherInfo">The account.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
@@ -116,10 +152,21 @@ namespace OpenAuth.App.SSO
return _httpContextAccessor.HttpContext.User.Identity.Name; return _httpContextAccessor.HttpContext.User.Identity.Name;
} }
var user = _cacheContext.Get<UserAuthSession>(GetToken()); var token = GetToken();
if (user != null) // 从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<UserAuthSession>(sessionId);
if (session != null)
{
return account;
}
}
} }
return ""; return "";
@@ -173,7 +220,12 @@ namespace OpenAuth.App.SSO
try try
{ {
_cacheContext.Remove(token); // 从JWT Token中提取会话ID删除缓存
var sessionId = GetSessionIdFromToken(token);
if (!string.IsNullOrEmpty(sessionId))
{
_cacheContext.Remove(sessionId);
}
return true; return true;
} }
catch catch

View File

@@ -13,6 +13,7 @@ using SqlSugar;
using Infrastructure.Extensions.AutofacManager; using Infrastructure.Extensions.AutofacManager;
using Infrastructure.Utilities; using Infrastructure.Utilities;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace OpenAuth.App.SSO namespace OpenAuth.App.SSO
@@ -24,11 +25,13 @@ namespace OpenAuth.App.SSO
protected ISqlSugarClient SugarClient; protected ISqlSugarClient SugarClient;
private ICacheContext _cacheContext; private ICacheContext _cacheContext;
private AppManager _appInfoService; private AppManager _appInfoService;
private IOptions<AppSetting> _appConfiguration;
public LoginParse( AppManager infoService, ICacheContext cacheContext, ISqlSugarClient client) public LoginParse( AppManager infoService, ICacheContext cacheContext, ISqlSugarClient client, IOptions<AppSetting> appConfiguration)
{ {
_appInfoService = infoService; _appInfoService = infoService;
_cacheContext = cacheContext; _cacheContext = cacheContext;
_appConfiguration = appConfiguration;
string tenantId = Define.DEFAULT_TENANT_ID; string tenantId = Define.DEFAULT_TENANT_ID;
var httpContextAccessor = AutofacContainerModule.GetService<IHttpContextAccessor>(); var httpContextAccessor = AutofacContainerModule.GetService<IHttpContextAccessor>();
if (httpContextAccessor != null) if (httpContextAccessor != null)
@@ -87,21 +90,33 @@ namespace OpenAuth.App.SSO
throw new Exception("账号状态异常,可能已停用"); throw new Exception("账号状态异常,可能已停用");
} }
// 生成唯一的会话ID作为缓存Key
var sessionId = Guid.NewGuid().ToString("N");
var expireDays = _appConfiguration.Value.JwtExpireDays;
var currentSession = new UserAuthSession var currentSession = new UserAuthSession
{ {
Account = model.Account, Account = model.Account,
Name = sysUserInfo.Name, Name = sysUserInfo.Name,
Token = Guid.NewGuid().ToString().GetHashCode().ToString("x"), Token = sessionId,
AppKey = model.AppKey, AppKey = model.AppKey,
CreateTime = TimeHelper.Now CreateTime = TimeHelper.Now
// , IpAddress = HttpContext.Current.Request.UserHostAddress
}; };
//创建Session //创建Session缓存,用于登出时失效判定
_cacheContext.Set(currentSession.Token, currentSession, TimeHelper.Now.AddDays(10)); _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.Code = 200;
result.Token = currentSession.Token; result.Token = jwtToken;
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -19,6 +19,8 @@
}, },
"UploadPath": "", //附件上传的路径,如果为空则保存在站点根目录 "UploadPath": "", //附件上传的路径,如果为空则保存在站点根目录
"RedisConf": "your_redis_server:6379,password=your_redis_password", //redis配置信息 "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过期天数
} }
} }