feat(identity): 接入认证中心多租户登录

支持 Manifest V2 动态 tid 验证、Tenant Context 同步和租户内 JIT 投影,并保留 Manifest V1 与旧 Session 兼容。\n\n增加 tenantHint、租户切换、普通注册关闭及 application/principal/tenant 两级 SSF 撤销;迁移、定向安全测试和本地双租户跨仓 E2E 已通过。\n\nrelease_required=true;未执行 Release、Staging 或真实链路。
This commit is contained in:
2026-07-28 17:28:35 +08:00
parent 0b02e62c72
commit 5c679ff13f
45 changed files with 2986 additions and 139 deletions
+16
View File
@@ -85,6 +85,12 @@ func (s *Service) Create(ctx context.Context, bundle TokenBundle, localUser *aut
if err != nil || verified == nil || verified.Source != "oidc" || verified.ID == "" || verified.ID != localUser.ID {
return "", ErrSessionInvalid
}
if localUser.OIDCUserBindingID != "" &&
(localUser.Issuer == "" || localUser.ApplicationID == "" || localUser.TenantID == "" || localUser.OIDCClientID == "" ||
verified.Issuer != localUser.Issuer || verified.ApplicationID != localUser.ApplicationID ||
verified.TenantID != localUser.TenantID || verified.OIDCClientID != localUser.OIDCClientID) {
return "", ErrSessionInvalid
}
now := s.now()
if !verified.TokenExpiresAt.After(now) {
return "", ErrSessionExpired
@@ -100,6 +106,8 @@ func (s *Service) Create(ctx context.Context, bundle TokenBundle, localUser *aut
}
_, err = s.repository.CreateOIDCSession(ctx, store.CreateOIDCSessionInput{
SessionTokenHash: hash, GatewayUserID: localUser.GatewayUserID, GatewayTenantID: localUser.GatewayTenantID,
OIDCUserBindingID: localUser.OIDCUserBindingID, OIDCClientID: verified.OIDCClientID,
Issuer: verified.Issuer, ApplicationID: verified.ApplicationID, TenantID: verified.TenantID,
TokenCiphertext: ciphertext, AccessTokenExpiresAt: verified.TokenExpiresAt,
LastSeenAt: now, IdleExpiresAt: now.Add(s.config.IdleTTL), AbsoluteExpiresAt: now.Add(s.config.AbsoluteTTL),
})
@@ -142,6 +150,9 @@ func (s *Service) resolveRecord(ctx context.Context, hash []byte, record store.O
}
user, err := s.verifySessionUser(ctx, record, bundle.AccessToken)
if err != nil {
if errors.Is(err, ErrSessionInvalid) {
_ = s.repository.DeleteOIDCSessionByID(ctx, record.ID)
}
return nil, err
}
if err := s.touch(ctx, record, now); err != nil {
@@ -264,6 +275,11 @@ func (s *Service) verifySessionUser(ctx context.Context, record store.OIDCSessio
if err != nil || user == nil || user.Source != "oidc" || user.ID != record.ExternalUserID {
return nil, ErrSessionInvalid
}
if record.OIDCUserBindingID != "" &&
(user.Issuer != record.Issuer || user.ApplicationID != record.ApplicationID ||
user.TenantID != record.TenantID || user.OIDCClientID != record.OIDCClientID) {
return nil, ErrSessionInvalid
}
return user, nil
}