| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package middleware_test
- import (
- "context"
- "database/sql"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
- "perms-system-server/internal/consts"
- "perms-system-server/internal/middleware"
- "perms-system-server/internal/model"
- productModel "perms-system-server/internal/model/product"
- productmemberModel "perms-system-server/internal/model/productmember"
- userModel "perms-system-server/internal/model/user"
- "perms-system-server/internal/response"
- "perms-system-server/internal/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TC-0749: L-B 修复回归 —— jwtauthMiddleware 必须优先判定 TokenVersion 失效,
- // 而不是 ProductStatus/MemberType。旧实现会在"产品已禁用"场景下先返回 403 ProductDisabled,
- // 使用户被强制退出时看到无关文案;修复后 TokenVersion 不一致应返回 401 "登录状态已失效,请重新登录"。
- func TestJwtAuthMiddleware_TokenVersionCheckedBeforeProductStatus(t *testing.T) {
- ctx := context.Background()
- conn := testutil.GetTestSqlConn()
- models := model.NewModels(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
- now := time.Now().Unix()
- // 1) 创建"已禁用"的产品
- pCode := "mw_ord_" + testutil.UniqueId()
- pRes, err := models.SysProductModel.Insert(ctx, &productModel.SysProduct{
- Code: pCode, Name: pCode, AppKey: pCode + "_k", AppSecret: "s",
- Status: consts.StatusDisabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- pId, _ := pRes.LastInsertId()
- // 2) 创建启用中的用户,TokenVersion=5
- username := "mw_ord_u_" + testutil.UniqueId()
- uRes, err := models.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: username, Password: "x", Nickname: "n",
- Avatar: sql.NullString{}, IsSuperAdmin: consts.IsSuperAdminNo,
- MustChangePassword: 2, Status: consts.StatusEnabled, TokenVersion: 5,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- userId, _ := uRes.LastInsertId()
- // 3) 该用户是禁用产品的 ADMIN 成员
- mRes, err := models.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
- ProductCode: pCode, UserId: userId, MemberType: consts.MemberTypeAdmin,
- Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- mId, _ := mRes.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
- testutil.CleanTable(ctx, conn, "`sys_user`", userId)
- testutil.CleanTable(ctx, conn, "`sys_product`", pId)
- })
- m, _ := newTestMiddleware()
- // 携带 stale TokenVersion=3 的 access token(DB 是 5)+ 禁用产品 code
- tokenStr := generateTestToken(testAccessSecret, 3600, &middleware.Claims{
- TokenType: consts.TokenTypeAccess,
- UserId: userId,
- Username: username,
- ProductCode: pCode,
- TokenVersion: 3,
- })
- handler := m.Handle(func(w http.ResponseWriter, r *http.Request) {
- t.Fatal("should not reach handler")
- })
- req := httptest.NewRequest(http.MethodPost, "/test", nil)
- req.Header.Set("Authorization", "Bearer "+tokenStr)
- rr := httptest.NewRecorder()
- handler.ServeHTTP(rr, req)
- var body response.Body
- require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body))
- assert.Equal(t, 401, body.Code,
- "L-B:TokenVersion 失配必须先于产品禁用被识别(返回 401 而非 403)")
- assert.Equal(t, "登录状态已失效,请重新登录", body.Msg,
- "L-B:文案必须是'登录状态已失效'而不是'该产品已被禁用',否则用户会被无关信息误导")
- }
- // TC-0750: L-B 修复回归 —— TokenVersion 匹配但产品被禁用,仍应返回 403 "该产品已被禁用"。
- // 保证修复未把所有场景都吞成 401。
- func TestJwtAuthMiddleware_ProductDisabledAfterVersionOk(t *testing.T) {
- ctx := context.Background()
- conn := testutil.GetTestSqlConn()
- models := model.NewModels(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
- now := time.Now().Unix()
- pCode := "mw_ord2_" + testutil.UniqueId()
- pRes, err := models.SysProductModel.Insert(ctx, &productModel.SysProduct{
- Code: pCode, Name: pCode, AppKey: pCode + "_k", AppSecret: "s",
- Status: consts.StatusDisabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- pId, _ := pRes.LastInsertId()
- username := "mw_ord2_u_" + testutil.UniqueId()
- uRes, err := models.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: username, Password: "x", Nickname: "n",
- Avatar: sql.NullString{}, IsSuperAdmin: consts.IsSuperAdminNo,
- MustChangePassword: 2, Status: consts.StatusEnabled, TokenVersion: 0,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- userId, _ := uRes.LastInsertId()
- mRes, err := models.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
- ProductCode: pCode, UserId: userId, MemberType: consts.MemberTypeAdmin,
- Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- mId, _ := mRes.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
- testutil.CleanTable(ctx, conn, "`sys_user`", userId)
- testutil.CleanTable(ctx, conn, "`sys_product`", pId)
- })
- m, _ := newTestMiddleware()
- tokenStr := generateTestToken(testAccessSecret, 3600, &middleware.Claims{
- TokenType: consts.TokenTypeAccess,
- UserId: userId,
- Username: username,
- ProductCode: pCode,
- TokenVersion: 0,
- })
- handler := m.Handle(func(w http.ResponseWriter, r *http.Request) {
- t.Fatal("should not reach handler")
- })
- req := httptest.NewRequest(http.MethodPost, "/test", nil)
- req.Header.Set("Authorization", "Bearer "+tokenStr)
- rr := httptest.NewRecorder()
- handler.ServeHTTP(rr, req)
- var body response.Body
- require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body))
- assert.Equal(t, 403, body.Code)
- assert.Equal(t, "该产品已被禁用", body.Msg)
- }
|