adminLoginLogic.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package pub
  2. import (
  3. "context"
  4. "crypto/subtle"
  5. "errors"
  6. "fmt"
  7. "time"
  8. "perms-system-server/internal/consts"
  9. authHelper "perms-system-server/internal/logic/auth"
  10. "perms-system-server/internal/middleware"
  11. "perms-system-server/internal/model/user"
  12. "perms-system-server/internal/response"
  13. "perms-system-server/internal/svc"
  14. "perms-system-server/internal/types"
  15. "github.com/zeromicro/go-zero/core/limit"
  16. "github.com/zeromicro/go-zero/core/logx"
  17. "golang.org/x/crypto/bcrypt"
  18. )
  19. type AdminLoginLogic struct {
  20. logx.Logger
  21. ctx context.Context
  22. svcCtx *svc.ServiceContext
  23. }
  24. func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminLoginLogic {
  25. return &AdminLoginLogic{
  26. Logger: logx.WithContext(ctx),
  27. ctx: ctx,
  28. svcCtx: svcCtx,
  29. }
  30. }
  31. // AdminLogin 管理后台登录。仅限超级管理员通过 managementKey + 用户名密码登录管理后台,返回 JWT 令牌对。
  32. func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.LoginResp, err error) {
  33. if subtle.ConstantTimeCompare([]byte(req.ManagementKey), []byte(l.svcCtx.Config.Auth.ManagementKey)) != 1 {
  34. return nil, response.ErrUnauthorized("managementKey无效")
  35. }
  36. // 限流 key 使用 "admin:<clientIP>:<username>" 双键维度:只有来自同一 IP + 同一 username 的
  37. // 连续失败才会累加计数,远端任意 IP 无法通过只打 username 把任意超管账号永久锁死(见审计 H-1)。
  38. // clientIP 由 AdminLoginRateLimit 中间件注入 context;解析失败时用 "unknown" 走共享桶兜底,
  39. // 仍有 5min/10 次的总体上限,避免退化为"无限流"。
  40. if l.svcCtx.UsernameLoginLimit != nil {
  41. clientIP := middleware.GetClientIP(l.ctx)
  42. if clientIP == "" {
  43. clientIP = "unknown"
  44. }
  45. key := fmt.Sprintf("admin:%s:%s", clientIP, req.Username)
  46. code, _ := l.svcCtx.UsernameLoginLimit.Take(key)
  47. if code == limit.OverQuota {
  48. return nil, response.NewCodeError(429, "登录尝试过于频繁,请5分钟后再试")
  49. }
  50. }
  51. u, err := l.svcCtx.SysUserModel.FindOneByUsername(l.ctx, req.Username)
  52. if err != nil {
  53. if errors.Is(err, user.ErrNotFound) {
  54. bcrypt.CompareHashAndPassword(dummyBcryptHash, []byte(req.Password))
  55. return nil, response.ErrUnauthorized("用户名或密码错误")
  56. }
  57. return nil, err
  58. }
  59. if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(req.Password)); err != nil {
  60. return nil, response.ErrUnauthorized("用户名或密码错误")
  61. }
  62. if u.Status != consts.StatusEnabled {
  63. return nil, response.ErrUnauthorized("用户名或密码错误")
  64. }
  65. if u.IsSuperAdmin != consts.IsSuperAdminYes {
  66. return nil, response.ErrUnauthorized("用户名或密码错误")
  67. }
  68. ud := l.svcCtx.UserDetailsLoader.Load(l.ctx, u.Id, "")
  69. accessToken, err := authHelper.GenerateAccessToken(
  70. l.svcCtx.Config.Auth.AccessSecret,
  71. l.svcCtx.Config.Auth.AccessExpire,
  72. ud.UserId, ud.Username, ud.ProductCode, ud.MemberType, ud.TokenVersion,
  73. )
  74. if err != nil {
  75. return nil, err
  76. }
  77. refreshToken, err := authHelper.GenerateRefreshToken(
  78. l.svcCtx.Config.Auth.RefreshSecret,
  79. l.svcCtx.Config.Auth.RefreshExpire,
  80. ud.UserId, ud.ProductCode, ud.TokenVersion,
  81. )
  82. if err != nil {
  83. return nil, err
  84. }
  85. return &types.LoginResp{
  86. AccessToken: accessToken,
  87. RefreshToken: refreshToken,
  88. Expires: time.Now().Unix() + l.svcCtx.Config.Auth.AccessExpire,
  89. UserInfo: types.UserInfo{
  90. UserId: ud.UserId,
  91. Username: ud.Username,
  92. Nickname: ud.Nickname,
  93. Avatar: ud.Avatar,
  94. Email: ud.Email,
  95. Phone: ud.Phone,
  96. IsSuperAdmin: ud.IsSuperAdminRaw,
  97. MustChangePassword: ud.MustChangePwdRaw,
  98. MemberType: ud.MemberType,
  99. Perms: ud.Perms,
  100. },
  101. }, nil
  102. }