mirror of
https://gitee.com/dotnetchina/OpenAuth.Net.git
synced 2026-07-14 03:23:48 +08:00
🐛fix: #IJX34Q 非Identity的token支持jwt token
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
144
OpenAuth.App/SSO/JwtTokenHelper.cs
Normal file
144
OpenAuth.App/SSO/JwtTokenHelper.cs
Normal 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中提取会话ID(jti)
|
||||||
|
/// </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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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中提取会话ID(jti),用于缓存查找
|
||||||
|
/// </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
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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过期天数
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user