| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- package auth
- import (
- "context"
- "database/sql"
- "errors"
- "testing"
- "time"
- "perms-system-server/internal/loaders"
- "perms-system-server/internal/middleware"
- userModel "perms-system-server/internal/model/user"
- "perms-system-server/internal/response"
- "perms-system-server/internal/svc"
- "perms-system-server/internal/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/limit"
- "github.com/zeromicro/go-zero/core/stores/redis"
- )
- // TC-0739: L-C 修复回归 —— Logout 必须受 TokenOpLimiter 保护;
- // 用 quota=2 的定制 limiter,同一用户超过配额后第 3 次必须返回 429,
- // 且该超限请求**不能**递增 tokenVersion(避免撞库者反复自增搅乱 Cache)。
- func TestLogout_TokenOpLimiter_BlocksThirdCall(t *testing.T) {
- ctx := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- username := "lg_rl_" + testutil.UniqueId()
- res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: username, Password: testutil.HashPassword("pw"), Nickname: "lg_rl",
- Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
- Status: 1, TokenVersion: 0, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- userId, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- cfg := testutil.GetTestConfig()
- rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
- // 独立 prefix 保证与全局 limiter 桶互不干扰,也避免用例互相污染
- svcCtx.TokenOpLimiter = limit.NewPeriodLimit(60, 2, rds, cfg.CacheRedis.KeyPrefix+":rl:logout:ut:"+testutil.UniqueId())
- lctx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: userId, Username: username, Status: 1,
- })
- require.NoError(t, NewLogoutLogic(lctx, svcCtx).Logout(), "第 1 次 logout 应放行")
- require.NoError(t, NewLogoutLogic(lctx, svcCtx).Logout(), "第 2 次 logout 仍在配额内应放行")
- err = NewLogoutLogic(lctx, svcCtx).Logout()
- require.Error(t, err, "第 3 次必须被 TokenOpLimiter 拦截")
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 429, ce.Code())
- assert.Contains(t, ce.Error(), "过于频繁")
- u, err := svcCtx.SysUserModel.FindOne(ctx, userId)
- require.NoError(t, err)
- assert.Equal(t, int64(2), u.TokenVersion,
- "被限流的 logout 请求绝不能再触发 IncrementTokenVersion(否则攻击者可反复刷新缓存)")
- }
- // TC-0740: L-C 修复 —— 限流 key 必须按 userId 隔离,A 用户打满不得影响 B 用户。
- func TestLogout_TokenOpLimiter_PerUserIsolated(t *testing.T) {
- ctx := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- mkUser := func(tag string) int64 {
- res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: "lg_iso_" + tag + "_" + testutil.UniqueId(),
- Password: testutil.HashPassword("pw"), Nickname: "lg_iso",
- Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
- Status: 1, TokenVersion: 0, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- return id
- }
- aId := mkUser("a")
- bId := mkUser("b")
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", aId, bId) })
- cfg := testutil.GetTestConfig()
- rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
- svcCtx.TokenOpLimiter = limit.NewPeriodLimit(60, 1, rds, cfg.CacheRedis.KeyPrefix+":rl:logout:iso:"+testutil.UniqueId())
- lctxA := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{UserId: aId, Status: 1})
- lctxB := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{UserId: bId, Status: 1})
- require.NoError(t, NewLogoutLogic(lctxA, svcCtx).Logout())
- // A 打满后再打一次
- err := NewLogoutLogic(lctxA, svcCtx).Logout()
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 429, ce.Code())
- require.NoError(t, NewLogoutLogic(lctxB, svcCtx).Logout(),
- "B 用户应当仍有独立配额,不被 A 用户的限流影响")
- }
|