GoWind 开源生态GoWind 开源生态
首页
框架
GoWind Admin
GoWind CMS
GoWind IM
GoWind UBA
GoWind IoT
GoWind Toolkit
GoWind Quant
GitHub
首页
框架
GoWind Admin
GoWind CMS
GoWind IM
GoWind UBA
GoWind IoT
GoWind Toolkit
GoWind Quant
GitHub
  • 介绍

    • GoWind Admin 产品介绍
    • GoWind Admin 安装指南
  • 后端文档

    • 后端架构总览
    • 后端核心模块详解
    • 后端 API 与 Protobuf 定义
    • 后端配置与部署
    • 后端扩展开发
  • 前端文档

    • 前端架构总览
    • 前端核心功能详解
  • 二开教程

    • 后端新增业务模块实战教程
    • 前端新增业务页面实战教程
    • 前后端联调完整实战教程
  • 高级教程

    • Lua 脚本扩展实战教程
    • 权限系统深度解析教程
    • 多租户架构实战教程
    • 任务调度与异步处理教程
    • 文件上传与对象存储教程
    • 事件总线与解耦架构教程
    • 前端主题定制与国际化教程
    • 性能优化与监控教程
    • SSE 实时推送
    • 登录策略与安全加固
    • 加密工具实战

登录策略与安全加固

GoWind Admin 提供了完整的身份认证与安全管控体系,涵盖登录策略、Token 管理、验证码、审计日志等多个安全层。本章详细介绍各安全机制的设计与使用。

一、安全架构概览

1.1 安全层级

GoWind Admin 的安全体系采用多层防护设计:

1.2 安全组件一览

组件路径职责
AuthenticationServiceinternal/service/authentication_service.go登录/登出/刷新/验证码
Authenticatorinternal/data/authenticator.goToken 创建/验证/撤销/封禁
UserTokenCacheinternal/data/user_token_cache.goToken 缓存与黑名单管理
LoginPolicyRepointernal/data/login_policy_repo.go登录策略数据持久化
LoginPolicyServiceinternal/service/login_policy_service.go登录策略 CRUD
Auth Middlewarepkg/middleware/auth/请求级别身份认证
Authorizerpkg/authorizer/权限授权引擎

二、登录认证流程

2.1 支持的授权类型

GoWind Admin 支持以下 OAuth 2.0 风格的授权类型(Grant Type):

Grant Type说明使用场景
password用户名密码登录管理后台登录
refresh_token刷新令牌Token 过期后无感刷新
client_credentials客户端凭据预留,暂未实现

2.2 密码登录流程

func (s *AuthenticationService) doGrantTypePassword(
    ctx context.Context, req *authenticationV1.LoginRequest,
) (*authenticationV1.LoginResponse, error) {
    // 1. 重置上下文(绕过隐私保护和权限检查)
    ctx = s.resetContextForLogin(ctx)

    // 2. 验证用户凭证
    _, err := s.userCredentialRepo.VerifyCredential(ctx, &authenticationV1.VerifyCredentialRequest{
        IdentityType: authenticationV1.UserCredential_USERNAME,
        Identifier:   req.GetUsername(),
        Credential:   req.GetPassword(),
        NeedDecrypt:  true,
    })

    // 3. 获取用户信息
    user, err := s.userRepo.Get(ctx, &identityV1.GetUserRequest{
        QueryBy: &identityV1.GetUserRequest_Username{Username: req.GetUsername()},
    })

    // 4. 构造 Token Payload
    tokenPayload := &authenticationV1.UserTokenPayload{
        UserId:   user.GetId(),
        TenantId: user.TenantId,
        Username: user.Username,
        ClientId: req.ClientId,
        DeviceId: req.DeviceId,
    }

    // 5. 解析用户权限信息
    err = s.resolveUserAuthority(ctx, user, tokenPayload)

    // 6. 生成令牌对
    accessToken, refreshToken, err := s.authenticator.CreateUserToken(ctx, req.GetClientType(), tokenPayload)

    return &authenticationV1.LoginResponse{
        TokenType:        authenticationV1.TokenType_bearer,
        AccessToken:      accessToken,
        RefreshToken:     trans.Ptr(refreshToken),
        ExpiresIn:        int64(s.authenticator.GetAccessTokenExpires(req.GetClientType()).Seconds()),
        RefreshExpiresIn: trans.Ptr(int64(s.authenticator.GetRefreshTokenExpires(req.GetClientType()).Seconds())),
    }, nil
}

关键步骤说明:

  1. 重置上下文:登录时没有用户身份信息,需要绕过 Ent 的隐私保护中间件和 Viewer 机制
  2. 凭证验证:支持用户名/密码校验,密码使用哈希比对
  3. 权限解析:根据用户-租户关系模型(一对一/一对多),收集角色和权限
  4. 令牌签发:生成 Access Token (JWT) + Refresh Token

2.3 Token 刷新流程

func (s *AuthenticationService) doGrantTypeRefreshToken(ctx context.Context, req *authenticationV1.LoginRequest) (*authenticationV1.LoginResponse, error) {
    // 1. 从当前 Token 中获取操作人信息
    operator, _ := auth.FromContext(ctx)

    // 2. 获取用户信息并重新解析权限
    user, _ := s.userRepo.Get(ctx, ...)
    tokenPayload := ...
    s.resolveUserAuthority(ctx, user, tokenPayload)

    // 3. 验证 Refresh Token
    s.authenticator.VerifyRefreshToken(ctx, req.GetClientType(), req.GetUserId(), operator.GetJti(), req.GetRefreshToken())

    // 4. 签发新的令牌对
    accessToken, refreshToken, _ := s.authenticator.CreateUserToken(ctx, req.GetClientType(), tokenPayload)

    return &authenticationV1.LoginResponse{...}, nil
}

刷新机制:

  • Refresh Token 使用后立即失效(一次性)
  • 签发新令牌对时,会重新解析用户权限(权限变更实时生效)
  • 旧 Access Token 通过 JTI 关联被撤销

三、Token 管理

3.1 Token 类型与过期时间

Token 类型过期时间存储位置
Access Token15 分钟Redis
Refresh Token7 天Redis
Token 黑名单可配置时长Redis

过期时间定义在 internal/data/authenticator.go:

const (
    DefaultAccessTokenExpires  = time.Minute * 15
    DefaultRefreshTokenExpires = time.Hour * 24 * 7
)

3.2 Token 验证流程

每次 API 请求都经过以下 Token 验证链:

func (a *Authenticator) Authenticate(ctx context.Context, req *authenticationV1.ValidateTokenRequest) (*authenticationV1.ValidateTokenResponse, error) {
    // 1. JWT 签名验证
    claims, err := authenticator.AuthenticateToken(req.GetToken())

    // 2. 过期时间检查
    if jwt.IsTokenExpired(claims) { ... }

    // 3. 解析 Token Payload
    payload, err := jwt.NewUserTokenPayloadWithClaims(claims)

    // 4. Redis 缓存验证(Token 是否存在且匹配)
    valid, err := a.userTokenCache.IsValidAccessToken(ctx, ...)

    // 5. 黑名单检查
    if a.userTokenCache.IsBlockedAccessToken(ctx, payload.GetJti()) { ... }

    return &authenticationV1.ValidateTokenResponse{IsValid: true, Payload: payload}, nil
}

五重验证:

  1. JWT 签名验证(防篡改)
  2. 过期时间检查(时效性)
  3. Payload 解析(数据完整性)
  4. Redis 缓存匹配(防伪造)
  5. 黑名单检查(即时封禁)

3.3 Token 封禁与解封

管理员可以封禁指定用户的 Token:

// 封禁 Token
func (a *Authenticator) BlockToken(ctx context.Context, req *authenticationV1.BlockTokenRequest) error {
    // 支持两种定位方式:
    // - 通过 Token 值定位
    // - 通过 JTI (JWT ID) 定位

    // 添加到黑名单,支持设置封禁时长
    return a.userTokenCache.AddBlockedAccessToken(ctx, jti, req.GetReason(), req.GetDuration().AsDuration())
}

// 解封 Token
func (a *Authenticator) UnblockToken(ctx context.Context, req *authenticationV1.UnblockTokenRequest) error {
    return a.userTokenCache.RevokeTokenByJti(ctx, req.GetClientType(), req.GetUserId(), jti)
}

3.4 多设备登录

系统支持多设备同时登录,通过 Redis 管理用户的多个 Token:

// 获取用户所有在线的 Access Token(用于 SSE 推送等场景)
func (a *Authenticator) GetAccessTokens(ctx context.Context, clientType authenticationV1.ClientType, userId uint32) []string {
    return a.userTokenCache.GetAccessTokens(ctx, clientType, userId)
}

3.5 登出流程

func (s *AuthenticationService) Logout(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
    operator, _ := auth.FromContext(ctx)
    // 撤销该用户的所有 Token
    s.authenticator.RevokeUserToken(ctx, s.clientType, operator.GetUserId())
    return &emptypb.Empty{}, nil
}

四、登录策略

4.1 策略模型

LoginPolicy(登录策略)用于控制用户的登录行为,数据模型包含以下字段:

字段类型说明
tenant_iduint32租户 ID(多租户隔离)
target_iduint32目标 ID(用户 ID 或 IP 地址)
typeenum策略类型
methodenum策略方法(允许/拒绝)
valuestring策略值
reasonstring原因说明

4.2 策略类型与方法

策略类型 (Type):

  • 基于 IP 地址的访问控制
  • 基于用户的访问控制
  • 基于时间段的访问控制

策略方法 (Method):

  • 允许 (Allow):白名单模式
  • 拒绝 (Deny):黑名单模式

4.3 策略 CRUD

LoginPolicyService 提供标准的 CRUD API:

type LoginPolicyService struct {
    adminV1.LoginPolicyServiceHTTPServer
    log  *log.Helper
    repo *data.LoginPolicyRepo
}

// 创建策略
func (s *LoginPolicyService) Create(ctx context.Context, req *authenticationV1.CreateLoginPolicyRequest) (*emptypb.Empty, error) {
    // 自动填充操作人信息
    operator, _ := auth.FromContext(ctx)
    req.Data.CreatedBy = trans.Ptr(operator.UserId)
    return &emptypb.Empty{}, s.repo.Create(ctx, req)
}

// 更新策略(支持 Upsert)
func (s *LoginPolicyService) Update(ctx context.Context, req *authenticationV1.UpdateLoginPolicyRequest) (*emptypb.Empty, error)) {
    // 如果设置了 AllowMissing 且记录不存在,则自动创建
    if req.GetAllowMissing() {
        exist, _ := r.IsExist(ctx, req.GetId())
        if !exist {
            return r.Create(ctx, &authenticationV1.CreateLoginPolicyRequest{Data: req.Data})
        }
    }
    // ...
}

4.4 使用场景示例

场景一:IP 黑名单

禁止特定 IP 地址登录管理后台:

{
  "target_id": 0,
  "type": "IP_BLACKLIST",
  "method": "DENY",
  "value": "192.168.1.100",
  "reason": "疑似恶意登录"
}

场景二:IP 白名单

仅允许公司内网 IP 登录:

{
  "target_id": 0,
  "type": "IP_WHITELIST",
  "method": "ALLOW",
  "value": "10.0.0.0/8",
  "reason": "仅允许内网访问"
}

场景三:用户封禁

封禁特定用户:

{
  "target_id": 12345,
  "type": "USER_BAN",
  "method": "DENY",
  "value": "永久封禁",
  "reason": "违反平台使用规范"
}

五、验证码机制

5.1 验证码生成

func (s *AuthenticationService) GenerateCaptcha(_ context.Context, _ *emptypb.Empty) (*authenticationV1.GenerateCaptchaResponse, error) {
    captchaId, captchaValue, _, err := s.captchaClient.Generate()
    return &authenticationV1.GenerateCaptchaResponse{
        CaptchaId:   captchaId,
        ImageBase64: captchaValue,
    }, nil
}

5.2 验证码校验

func (s *AuthenticationService) VerifyCaptcha(ctx context.Context, req *authenticationV1.VerifyCaptchaRequest) (*authenticationV1.VerifyCaptchaResponse, error) {
    ok, err := s.captchaClient.Verify(ctx, req.GetCaptchaId(), req.GetUserInput())
    return &authenticationV1.VerifyCaptchaResponse{Valid: ok}, nil
}

5.3 前端集成流程

Tips

验证码校验和登录是两个独立的 API 调用。前端先调用验证码校验接口,通过后再发起登录请求。

六、审计日志

6.1 审计日志类型

GoWind Admin 记录了多种类型的审计日志:

日志类型Service说明
API 审计日志ApiAuditLogService记录所有 API 请求
登录审计日志LoginAuditLogService记录登录/登出事件
操作审计日志OperationAuditLogService记录数据变更操作
权限审计日志PermissionAuditLogService记录权限策略变更
数据访问审计DataAccessAuditLogService记录敏感数据访问
策略评估日志PolicyEvaluationLogService记录权限评估结果

6.2 审计日志写入

审计日志通过中间件自动写入,在 rest_server.go 中配置:

func NewRestMiddleware(
    ctx *bootstrap.Context,
    accessTokenChecker auth.AccessTokenChecker,
    authorizer *authorizer.Authorizer,
    apiAuditLogRepo *data.ApiAuditLogRepo,
    loginLogRepo *data.LoginAuditLogRepo,
) []middleware.Middleware {
    ms = append(ms, applogging.Server(
        applogging.WithWriteApiLogFunc(func(ctx context.Context, data *auditV1.ApiAuditLog) error {
            return apiAuditLogRepo.Create(ctx, &auditV1.CreateApiAuditLogRequest{Data: data})
        }),
        applogging.WithWriteLoginLogFunc(func(ctx context.Context, data *auditV1.LoginAuditLog) error {
            return loginLogRepo.Create(ctx, &auditV1.CreateLoginAuditLogRequest{Data: data})
        }),
    ))
    // ...
}

Tips

审计日志目前采用同步写入数据库。如果系统负载较大,建议改为异步方式(通过 Asynq 任务队列投递)。

七、中间件安全链

7.1 REST 中间件顺序

// 1. Kratos 内置日志中间件
ms = append(ms, logging.Server(ctx.GetLogger()))

// 2. 自定义审计日志中间件(API 日志 + 登录日志)
ms = append(ms, applogging.Server(...))

// 3. 身份认证 + 权限授权(白名单机制)
ms = append(ms, selector.Server(
    auth.Server(                           // JWT Token 验证
        auth.WithAccessTokenChecker(accessTokenChecker),
        auth.WithInjectMetadata(false),
        auth.WithInjectEnt(true),
    ),
    authz.Server(authorizer.Engine()),    // Casbin/OPA 权限检查
).Match(rpc.NewRestWhiteListMatcher()).Build())

7.2 白名单机制

登录和验证码等接口不需要 Token 认证,通过白名单配置:

rpc.AddWhiteList(
    adminV1.OperationAuthenticationServiceLogin,            // 登录
    adminV1.OperationAuthenticationServiceGenerateCaptcha,  // 生成验证码
    adminV1.OperationAuthenticationServiceVerifyCaptcha,    // 校验验证码
)

7.3 隐私保护

登录流程需要绕过 Ent 的隐私保护中间件:

func (s *AuthenticationService) resetContextForLogin(ctx context.Context) context.Context {
    // 使用空的 NoopContext(无 Viewer 信息)
    ctx = viewer.WithContext(ctx, viewer.NewNoopContext())
    // 绕过隐私保护
    ctx = privacy.DecisionContext(ctx, privacy.Allow)
    return ctx
}

八、最佳实践

8.1 Token 安全

  • 短期 Token:Access Token 有效期仅 15 分钟,减少被盗用的风险窗口
  • Refresh Token 一次性使用:每次刷新后旧 Refresh Token 立即失效
  • 多维度验证:JWT 签名 + Redis 缓存 + 黑名单,三重保障
  • 即时封禁:通过 BlockToken 可立即封禁指定用户的 Token

8.2 密码安全

  • 密码使用哈希存储(CredentialType: PASSWORD_HASH)
  • 传输时使用 HTTPS 加密
  • 支持 NeedDecrypt 参数控制是否需要解密前端传来的密码

8.3 生产环境建议

  • 修改 JWT 密钥(authn.jwt.key),使用高强度随机密钥
  • 根据实际需求调整 Token 过期时间
  • 启用验证码防止暴力破解
  • 配置登录策略限制 IP 访问范围
  • 定期审查审计日志

相关文档:

  • 权限系统
  • 多租户
  • 后端架构
  • SSE 实时推送
Edit this page
Last Updated:: 6/5/26, 12:13 PM
Contributors: Bobo
Prev
SSE 实时推送
Next
加密工具实战