做统一认证登录

This commit is contained in:
2026-09-19 21:44:04 +08:00
parent 1a4471e34d
commit fa281363fb
78 changed files with 8127 additions and 1442 deletions
+43
View File
@@ -0,0 +1,43 @@
package models
import "time"
// 接入应用类型
const (
AuthAppTypeWeb = 1 // 有后端的 Web 应用(可安全保存 secret)
AuthAppTypeSPA = 2 // 单页应用(只能走 PKCE,不存 secret)
AuthAppTypeNative = 3 // 原生 APP / uniapp
AuthAppTypeMiniProg = 4 // 小程序
)
// 领域隔离:租户域与平台域使用不同 realm,避免平台账号登录租户应用
const (
AuthRealmTenant = "tenant"
AuthRealmPlatform = "platform"
)
// AuthClient 接入应用(OIDC Client):yz_auth_client
// 以后每开发一个新软件,只需在这里注册一条即可接入统一认证。
type AuthClient struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
ClientID string `orm:"column(client_id);size(64)" json:"client_id"`
ClientSecret *string `orm:"column(client_secret);size(128);null" json:"-"`
AppCode string `orm:"column(app_code);size(32)" json:"app_code"`
Name string `orm:"column(name);size(128)" json:"name"`
AppType int8 `orm:"column(app_type);default(1)" json:"app_type"`
RedirectURIs *string `orm:"column(redirect_uris);type(text);null" json:"redirect_uris"`
PostLogoutURIs *string `orm:"column(post_logout_uris);type(text);null" json:"post_logout_uris"`
BackchannelLogoutURI *string `orm:"column(backchannel_logout_uri);size(500);null" json:"backchannel_logout_uri"`
GrantTypes string `orm:"column(grant_types);size(255)" json:"grant_types"`
Scope *string `orm:"column(scope);size(255);null" json:"scope"`
AccessTTL int `orm:"column(access_ttl);default(1800)" json:"access_ttl"`
RefreshTTL int `orm:"column(refresh_ttl);default(2592000)" json:"refresh_ttl"`
Realm string `orm:"column(realm);size(16);default(tenant)" json:"realm"`
Status int8 `orm:"column(status);default(1)" json:"status"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
}
func (m *AuthClient) TableName() string {
return "yz_auth_client"
}
+72
View File
@@ -0,0 +1,72 @@
package models
import "time"
// 身份状态
const (
AuthIdentityStatusEnabled = 1 // 启用
AuthIdentityStatusDisabled = 0 // 禁用
AuthIdentityStatusLocked = 2 // 锁定(连续失败过多)
)
// AuthIdentity 统一身份(自然人):yz_auth_identity
//
// 统一认证中心的核心表。密码上提到这一层,一个自然人一份密码,
// 通过 yz_auth_tenant_user 绑定到多家企业(一人可在多家企业任职)。
//
// password_hash 存储 PHC 格式($argon2id$v=19$m=...,t=...,p=...$salt$hash),
// 盐与参数内联,无需独立 salt 列;历史数据为 salt$sha256(legacy),
// 首次登录成功时自动重新哈希为 argon2id。
type AuthIdentity struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
UnionID string `orm:"column(union_id);size(32)" json:"union_id"`
Mobile *string `orm:"column(mobile);size(20);null" json:"mobile"`
Email *string `orm:"column(email);size(128);null" json:"email"`
PasswordAlgo string `orm:"column(password_algo);size(16);default(argon2id)" json:"password_algo"`
PasswordHash *string `orm:"column(password_hash);size(255);null" json:"-"`
PasswordVer int `orm:"column(password_ver);default(1)" json:"password_ver"`
Nickname *string `orm:"column(nickname);size(64);null" json:"nickname"`
Avatar *string `orm:"column(avatar);size(500);null" json:"avatar"`
MfaEnabled int8 `orm:"column(mfa_enabled);default(0)" json:"mfa_enabled"`
MfaSecret *string `orm:"column(mfa_secret);size(128);null" json:"-"`
Status int8 `orm:"column(status);default(1)" json:"status"`
FailCount int `orm:"column(fail_count);default(0)" json:"fail_count"`
LockedUntil *time.Time `orm:"column(locked_until);type(datetime);null" json:"locked_until"`
LastLoginAt *time.Time `orm:"column(last_login_at);type(datetime);null" json:"last_login_at"`
LastLoginIP *string `orm:"column(last_login_ip);size(45);null" json:"last_login_ip"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *AuthIdentity) TableName() string {
return "yz_auth_identity"
}
// 第三方登录 provider 常量
const (
IdPWechat = "wechat"
IdPDingTalk = "dingtalk"
IdPFeishu = "feishu"
IdPQQ = "qq"
IdPGitHub = "github"
IdPGoogle = "google"
)
// AuthIdentityThird 第三方身份绑定:yz_auth_identity_third
// 同一身份可绑定多个第三方账号;同一 provider 的 open_id 全局唯一。
type AuthIdentityThird struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
Provider string `orm:"column(provider);size(32)" json:"provider"`
OpenID string `orm:"column(open_id);size(128)" json:"open_id"`
UnionID *string `orm:"column(union_id);size(128);null" json:"union_id"`
Nickname *string `orm:"column(nickname);size(128);null" json:"nickname"`
Avatar *string `orm:"column(avatar);size(500);null" json:"avatar"`
Raw *string `orm:"column(raw);type(text);null" json:"raw"`
BindTime time.Time `orm:"column(bind_time);auto_now_add;type(datetime)" json:"bind_time"`
}
func (m *AuthIdentityThird) TableName() string {
return "yz_auth_identity_third"
}
+26
View File
@@ -0,0 +1,26 @@
package models
import "time"
// AuthLoginLog 统一登录日志:yz_auth_login_log
// 认证中心上线后逐步替代 yz_system_login_log,补充 client_id / amr 字段。
type AuthLoginLog struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid *uint64 `orm:"column(tid);null" json:"tid"`
IdentityID *uint64 `orm:"column(identity_id);null" json:"identity_id"`
Account string `orm:"column(account);size(64);default('')" json:"account"`
UserName string `orm:"column(user_name);size(64);default('')" json:"user_name"`
TenantName string `orm:"column(tenant_name);size(64);default('')" json:"tenant_name"`
ClientID string `orm:"column(client_id);size(64);default('')" json:"client_id"`
LoginType string `orm:"column(login_type);size(20);default(password)" json:"login_type"`
Amr *string `orm:"column(amr);size(64);null" json:"amr"`
Status int8 `orm:"column(status);default(1)" json:"status"`
Message string `orm:"column(message);size(255);default('')" json:"message"`
IP string `orm:"column(ip);size(45);default('')" json:"ip"`
UserAgent string `orm:"column(user_agent);size(500);default('')" json:"user_agent"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *AuthLoginLog) TableName() string {
return "yz_auth_login_log"
}
+94
View File
@@ -0,0 +1,94 @@
package models
import "time"
// 会话吊销原因
const (
RevokeReasonLogout = "logout" // 用户主动登出
RevokeReasonKicked = "kicked" // 被新登录踢下线(1号1机)
RevokeReasonAdmin = "admin" // 管理员强制下线
RevokeReasonExpired = "expired" // 过期清理
)
// AuthSession 登录会话:yz_auth_session
// 用于「1号1机」并发控制、在线设备列表、强制下线。
type AuthSession struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Sid string `orm:"column(sid);size(64)" json:"sid"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
ClientID string `orm:"column(client_id);size(64);default('')" json:"client_id"`
DeviceID *string `orm:"column(device_id);size(64);null" json:"device_id"`
DeviceName *string `orm:"column(device_name);size(128);null" json:"device_name"`
IP *string `orm:"column(ip);size(45);null" json:"ip"`
UserAgent *string `orm:"column(user_agent);size(500);null" json:"user_agent"`
LoginType string `orm:"column(login_type);size(20);default(password)" json:"login_type"`
Amr *string `orm:"column(amr);size(64);null" json:"amr"`
LoginAt time.Time `orm:"column(login_at);type(datetime)" json:"login_at"`
LastAccessAt time.Time `orm:"column(last_access_at);type(datetime)" json:"last_access_at"`
ExpiresAt time.Time `orm:"column(expires_at);type(datetime)" json:"expires_at"`
Revoked int8 `orm:"column(revoked);default(0)" json:"revoked"`
RevokeReason *string `orm:"column(revoke_reason);size(64);null" json:"revoke_reason"`
RevokeAt *time.Time `orm:"column(revoke_at);type(datetime);null" json:"revoke_at"`
}
func (m *AuthSession) TableName() string {
return "yz_auth_session"
}
// AuthRefreshToken 刷新令牌:yz_auth_refresh_token
// 明文仅在颁发时返回一次,库中只存哈希;轮换时通过 family_id 检测重放。
type AuthRefreshToken struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TokenHash string `orm:"column(token_hash);size(128)" json:"token_hash"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
ClientID string `orm:"column(client_id);size(64);default('')" json:"client_id"`
Sid string `orm:"column(sid);size(64);default('')" json:"sid"`
FamilyID string `orm:"column(family_id);size(64)" json:"family_id"`
RotatedFrom *string `orm:"column(rotated_from);size(128);null" json:"rotated_from"`
Used int8 `orm:"column(used);default(0)" json:"used"`
Revoked int8 `orm:"column(revoked);default(0)" json:"revoked"`
ExpiresAt time.Time `orm:"column(expires_at);type(datetime)" json:"expires_at"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *AuthRefreshToken) TableName() string {
return "yz_auth_refresh_token"
}
// AuthCode 授权码:yz_auth_code
// 一次性、60 秒有效,配合 PKCE(S256)使用。
type AuthCode struct {
CodeHash string `orm:"column(code_hash);size(128);pk" json:"code_hash"`
ClientID string `orm:"column(client_id);size(64)" json:"client_id"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
RedirectURI string `orm:"column(redirect_uri);size(500)" json:"redirect_uri"`
CodeChallenge string `orm:"column(code_challenge);size(128)" json:"code_challenge"`
CodeChallengeMethod string `orm:"column(code_challenge_method);size(10);default(S256)" json:"code_challenge_method"`
Scope *string `orm:"column(scope);size(255);null" json:"scope"`
Nonce *string `orm:"column(nonce);size(128);null" json:"nonce"`
Used int8 `orm:"column(used);default(0)" json:"used"`
ExpiresAt time.Time `orm:"column(expires_at);type(datetime)" json:"expires_at"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *AuthCode) TableName() string {
return "yz_auth_code"
}
// AuthTokenBlacklist 令牌吊销表:yz_auth_token_blacklist
// 记录已登出/被踢下线的 access token 的 jti,过期后可定期清理。
type AuthTokenBlacklist struct {
Jti string `orm:"column(jti);size(64);pk" json:"jti"`
IdentityID uint64 `orm:"column(identity_id);default(0)" json:"identity_id"`
Sid *string `orm:"column(sid);size(64);null" json:"sid"`
Reason *string `orm:"column(reason);size(64);null" json:"reason"`
ExpiresAt time.Time `orm:"column(expires_at);type(datetime)" json:"expires_at"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *AuthTokenBlacklist) TableName() string {
return "yz_auth_token_blacklist"
}
+66
View File
@@ -0,0 +1,66 @@
package models
import "time"
// 租户自带身份源 provider
const (
TenantIdPDingTalk = "dingtalk"
TenantIdPFeishu = "feishu"
TenantIdPWeWork = "wework"
TenantIdPOIDC = "oidc"
TenantIdPSAML = "saml"
)
// AuthTenantIdp 租户自带身份源:yz_auth_tenant_idp
// 企业客户可配置自己的钉钉/飞书/企业微信或标准 OIDC/SAML 上游,
// 登录时按 tid 路由到对应身份源(第10条需求)。
type AuthTenantIdp struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid uint64 `orm:"column(tid)" json:"tid"`
Provider string `orm:"column(provider);size(32)" json:"provider"`
Name *string `orm:"column(name);size(128);null" json:"name"`
AppID *string `orm:"column(app_id);size(128);null" json:"app_id"`
AppSecret *string `orm:"column(app_secret);size(512);null" json:"-"`
Issuer *string `orm:"column(issuer);size(500);null" json:"issuer"`
AuthURL *string `orm:"column(auth_url);size(500);null" json:"auth_url"`
TokenURL *string `orm:"column(token_url);size(500);null" json:"token_url"`
UserinfoURL *string `orm:"column(userinfo_url);size(500);null" json:"userinfo_url"`
JwksURL *string `orm:"column(jwks_url);size(500);null" json:"jwks_url"`
Scopes *string `orm:"column(scopes);size(255);null" json:"scopes"`
AttrMap *string `orm:"column(attr_map);type(text);null" json:"attr_map"`
ProxyURL *string `orm:"column(proxy_url);size(500);null" json:"proxy_url"`
Status int8 `orm:"column(status);default(1)" json:"status"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
}
func (m *AuthTenantIdp) TableName() string {
return "yz_auth_tenant_idp"
}
// 并发超限策略
const (
KickStrategyKickOld = 1 // 踢掉旧会话(默认)
KickStrategyReject = 2 // 拒绝新登录
)
// AuthTenantAuthConfig 租户登录策略:yz_auth_tenant_auth_config
// 每个租户可自定义验证码方式、密码强度、会话时长与并发设备数。
type AuthTenantAuthConfig struct {
Tid uint64 `orm:"column(tid);pk" json:"tid"`
VerifyType string `orm:"column(verify_type);size(20);default(captcha)" json:"verify_type"`
OpenVerify int8 `orm:"column(open_verify);default(1)" json:"open_verify"`
PwdMinLen int `orm:"column(pwd_min_len);default(8)" json:"pwd_min_len"`
PwdComplexity int8 `orm:"column(pwd_complexity);default(0)" json:"pwd_complexity"`
SessionTTL int `orm:"column(session_ttl);default(7200)" json:"session_ttl"`
MaxSession int `orm:"column(max_session);default(1)" json:"max_session"`
KickStrategy int8 `orm:"column(kick_strategy);default(1)" json:"kick_strategy"`
MfaRequired int8 `orm:"column(mfa_required);default(0)" json:"mfa_required"`
IPWhitelist *string `orm:"column(ip_whitelist);type(text);null" json:"ip_whitelist"`
AllowThird *string `orm:"column(allow_third);size(255);null" json:"allow_third"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
}
func (m *AuthTenantAuthConfig) TableName() string {
return "yz_auth_tenant_auth_config"
}
+28
View File
@@ -0,0 +1,28 @@
package models
import "time"
// AuthTenantUser 身份-企业绑定:yz_auth_tenant_user
//
// 一个 AuthIdentity 可有多条绑定(在多家企业任职),各企业内
// 独立维护姓名、部门、角色与启用状态;密码不属于这一层。
type AuthTenantUser struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid uint64 `orm:"column(tid)" json:"tid"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
Account *string `orm:"column(account);size(64);null" json:"account"`
Name *string `orm:"column(name);size(64);null" json:"name"`
Phone *string `orm:"column(phone);size(20);null" json:"phone"`
Email *string `orm:"column(email);size(128);null" json:"email"`
GroupID uint64 `orm:"column(group_id);default(0)" json:"group_id"`
OrgID uint64 `orm:"column(org_id);default(0)" json:"org_id"`
IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"`
Status int8 `orm:"column(status);default(1)" json:"status"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *AuthTenantUser) TableName() string {
return "yz_auth_tenant_user"
}
+15 -2
View File
@@ -97,6 +97,8 @@ func Init(_ string) {
new(TenantCrmContract),
new(TenantCrmPayback),
new(TenantCrmPaybackRecord),
// 平台更新通知(docs/sql/platform_upgrade.sql)
new(PlatformUpgradeNotice),
new(ErpAccountSet),
new(ErpNormalSetting),
new(ErpCompanyContact),
@@ -176,6 +178,19 @@ func Init(_ string) {
new(WechatMpConfig),
new(WechatMpFollower),
new(WechatMpVerifyCode),
// 统一认证中心(UAC):docs/sql/create_auth_tables.sql,人工执行建表
new(AuthIdentity),
new(AuthIdentityThird),
new(AuthTenantUser),
new(AuthClient),
new(AuthSession),
new(AuthRefreshToken),
new(AuthCode),
new(AuthTokenBlacklist),
new(AuthTenantIdp),
new(AuthTenantAuthConfig),
new(AuthLoginLog),
)
// 创建全局 Ormer
@@ -192,5 +207,3 @@ func Init(_ string) {
// 新建与调整走 SQL:docs/sql/update_role_system.sql,代码不做自动补建。
Orm = orm.NewOrm()
}
+23
View File
@@ -0,0 +1,23 @@
package models
import "time"
// PlatformUpgradeNotice 平台更新通知记录表:yz_platform_upgrade_notice
// 用于展示平台近期更新内容,支持按时间排序展示
type PlatformUpgradeNotice struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"` // 租户 ID,0 表示全局
UpdateDate string `orm:"column(update_date);size(20)" json:"update_date"` // 更新日期 YYYY-MM-DD
Title string `orm:"column(title);size(255)" json:"title"` // 更新标题
Content string `orm:"column(content);type(text)" json:"content"` // 更新内容详情
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序权重,越大越靠前
Status int8 `orm:"column(status);default(1)" json:"status"` // 状态:0-隐藏 1-显示
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *PlatformUpgradeNotice) TableName() string {
return "yz_platform_upgrade_notice"
}