using System.Security.Claims;
using Dpz.Core.Service.Mediator.Features.Security.Queries;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace Dpz.Core.Auth.Service;
public sealed class OidcSignInManager(
UserManager<VmUserInfo> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<VmUserInfo> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILoggerFactory loggerFactory,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<VmUserInfo> confirmation,
IMediator mediator,
IFusionCache fusionCache,
IAccountSessionService accountSessionService
)
: SignInManager<VmUserInfo>(
userManager,
contextAccessor,
claimsFactory,
optionsAccessor,
loggerFactory.CreateLogger<SignInManager<VmUserInfo>>(),
schemes,
confirmation
)
{
public override async Task SignInAsync(
VmUserInfo user,
AuthenticationProperties authenticationProperties,
string? authenticationMethod = null
)
{
InitSessionId(user);
var userPrincipalAsync = await CreateUserPrincipalAsync(user);
if (authenticationMethod != null)
{
userPrincipalAsync
.Identities.First()
.AddClaim(
new Claim(
"https://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod",
authenticationMethod
)
);
}
await CacheUserSessionAsync(user);
await Context.SignInAsync(
ConstValues.DefaultScheme,
userPrincipalAsync,
authenticationProperties
);
}
private readonly IUserClaimsPrincipalFactory<VmUserInfo> _claimsFactory = claimsFactory;
private readonly ILogger<SignInManager<VmUserInfo>> _logger =
loggerFactory.CreateLogger<OidcSignInManager>();
public override async Task<SignInResult> PasswordSignInAsync(
string userName,
string password,
bool isPersistent,
bool lockoutOnFailure
)
{
var verifyResult = await mediator.Send(
new VerifyAccountPasswordRequest { Account = userName, Password = password }
);
var userInfo = verifyResult.Data;
// 检查用户是否存在且密码是否正确
if (!verifyResult.Success || userInfo == null)
{
return SignInResult.Failed;
}
// 检查用户是否被禁用
if (userInfo.Enable == false)
{
_logger.LogWarning("用户 {UserName} 被禁用,登录被拒绝", userName);
// 设置标记以表示账户被禁用
Context.SetAccountDisabled();
return SignInResult.Failed;
}
var result = await SignInOrTwoFactorAsync(userInfo, isPersistent);
return result;
}
public override async Task RefreshSignInAsync(VmUserInfo user)
{
var authenticateResult = await Context.AuthenticateAsync();
var sessionId = authenticateResult.Principal.SessionId;
if (!string.IsNullOrWhiteSpace(sessionId))
{
user.SessionId = sessionId;
}
await CacheUserSessionAsync(user);
await Context.SignInAsync(
ConstValues.DefaultScheme,
await _claimsFactory.CreateAsync(user),
authenticateResult.Properties
);
}
/// <summary>
/// 判断应用程序cookie身份
/// </summary>
public override bool IsSignedIn(ClaimsPrincipal principal)
{
_logger.LogInformation("IsSignedIn");
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.Identities.Any(i => i.AuthenticationType == ConstValues.DefaultScheme);
}
/// <summary>
/// 登录指定的用户 <see cref="VmUserInfo"/>
/// </summary>
public override async Task SignInWithClaimsAsync(
VmUserInfo user,
AuthenticationProperties? authenticationProperties,
IEnumerable<Claim> additionalClaims
)
{
InitSessionId(user);
var userPrincipal = await CreateUserPrincipalAsync(user);
foreach (var claim in additionalClaims)
{
userPrincipal.Identities.First().AddClaim(claim);
}
await CacheUserSessionAsync(user);
await Context.SignInAsync(
ConstValues.DefaultScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties()
);
}
private async Task CacheUserSessionAsync(VmUserInfo user)
{
InitSessionId(user);
var sessionId = user.SessionId!;
await fusionCache.SetAsync(UserSessionKeys.InfoKey(sessionId), user, TimeSpan.FromDays(30));
await accountSessionService.CreateOrRefreshAsync(user);
}
private static void InitSessionId(VmUserInfo user)
{
if (string.IsNullOrWhiteSpace(user.SessionId))
{
user.SessionId = Guid.NewGuid().ToString("N");
}
}
}
/// <summary>
/// SignInResult 扩展方法,用于标记不同的失败原因
/// </summary>
public static class SignInResultExtensions
{
private const string AccountDisabledKey = "AccountDisabled";
/// <summary>
/// 设置账户被禁用标记到 HttpContext
/// </summary>
public static void SetAccountDisabled(this HttpContext context)
{
context.Items[AccountDisabledKey] = true;
}
/// <summary>
/// 检查 HttpContext 中是否有账户被禁用标记
/// </summary>
public static bool HasAccountDisabledFlag(this HttpContext context)
{
return context.Items.ContainsKey(AccountDisabledKey);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个自定义的 OIDC(OpenID Connect)登录管理器实现,继承自 ASP.NET Core Identity 的 SignInManager<VmUserInfo>。主要用于处理用户身份验证、登录和会话管理。
主要组件
OidcSignInManager 类
这是核心类,通过主构造函数注入了多个依赖项:
UserManager:用户管理器IHttpContextAccessor:HTTP 上下文访问器IMediator:中介者模式实现(用于发送命令/查询)IFusionCache:缓存服务IAccountSessionService:账户会话服务
核心方法解析
1. SignInAsync - 用户登录
public override async Task SignInAsync(...)
- 初始化会话 ID
- 创建用户主体(ClaimsPrincipal)
- 可选添加认证方法声明
- 缓存用户会话
- 执行实际的登录操作
2. PasswordSignInAsync - 密码登录
public override async Task<SignInResult> PasswordSignInAsync(...)
- 通过
Mediator验证用户名和密码 - 检查账户是否存在
- 关键业务逻辑:检查账户是否被禁用
- 如果禁用,记录日志并在 HttpContext 中设置禁用标记
- 返回登录失败结果
- 成功则执行登录或双因素认证流程
3. RefreshSignInAsync - 刷新登录状态
public override async Task RefreshSignInAsync(VmUserInfo user)
- 获取当前认证结果和会话 ID
- 更新用户会话缓存
- 重新签发认证 Cookie
4. IsSignedIn - 检查用户是否已登录
public override bool IsSignedIn(ClaimsPrincipal principal)
- 验证用户主体的认证类型是否匹配默认方案
5. SignInWithClaimsAsync - 带额外声明的登录
public override async Task SignInWithClaimsAsync(...)
- 支持添加额外的用户声明(Claims)
- 适用于需要自定义用户属性的场景
私有辅助方法
CacheUserSessionAsync
- 缓存用户会话信息到 FusionCache(30天有效期)
- 通过
IAccountSessionService创建或刷新会话记录
InitSessionId
- 如果会话 ID 为空,生成新的 GUID 作为会话标识
SignInResultExtensions 扩展类
提供了两个静态扩展方法:
SetAccountDisabled
在 HttpContext.Items 中设置账户禁用标记,用于跨中间件传递状态信息。
HasAccountDisabledFlag
检查 HttpContext 是否包含账户禁用标记,可用于后续中间件或过滤器中判断失败原因。
设计亮点
- 会话管理:双重存储(缓存 + 服务层),确保会话持久性
- 安全增强:检查账户启用状态,防止禁用账户登录
- 可扩展性:通过 Mediator 模式解耦验证逻辑
- 状态传递:使用 HttpContext.Items 传递特定失败原因
- 日志记录:关键操作都有日志追踪
使用场景
适用于需要 OIDC 标准兼容的身份验证系统,支持:
- 标准用户名密码登录
- 会话持久化
- 账户状态控制(启用/禁用)
- 自定义声明扩展
AI 正在分析代码…
评论加载中...