From f74c8f1f24850b6736fba3b44ab66ad98d7a522d Mon Sep 17 00:00:00 2001 From: xuans Date: Sun, 30 Aug 2026 20:36:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=AF=86=E7=A0=81=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E8=AF=B7=E6=B1=82=E8=A7=A3=E6=9E=90=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DJSON=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/httpapi/json.go | 6 ++--- .../httpapi/password_reset_account.go | 6 +---- .../httpapi/password_reset_account_test.go | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 backend/internal/httpapi/password_reset_account_test.go diff --git a/backend/internal/httpapi/json.go b/backend/internal/httpapi/json.go index 0505a36..e2dfe2e 100644 --- a/backend/internal/httpapi/json.go +++ b/backend/internal/httpapi/json.go @@ -9,10 +9,10 @@ import ( "github.com/gin-gonic/gin" ) -// bindJSON ???? JSON ?????????????????? JSON? +// bindJSON 严格解析 JSON 请求,并拒绝未知字段和多段 JSON。 func bindJSON(c *gin.Context, target any, limit int64) error { if c == nil || c.Request == nil || c.Request.Body == nil { - return errors.New("???????") + return errors.New("请求上下文或请求体为空") } if limit > 0 { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit) @@ -24,7 +24,7 @@ func bindJSON(c *gin.Context, target any, limit int64) error { } if err := decoder.Decode(&struct{}{}); err != io.EOF { if err == nil { - return errors.New("????????? JSON ??") + return errors.New("请求体包含多段 JSON 数据") } return err } diff --git a/backend/internal/httpapi/password_reset_account.go b/backend/internal/httpapi/password_reset_account.go index 48c722c..53127b1 100644 --- a/backend/internal/httpapi/password_reset_account.go +++ b/backend/internal/httpapi/password_reset_account.go @@ -1,7 +1,6 @@ package httpapi import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -27,11 +26,8 @@ type passwordResetResolution struct { // resolvePasswordResetAccount 根据账户标识返回对应的密码处理内容。 func (s *Server) resolvePasswordResetAccount(c *gin.Context) { - c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, passwordResetResolveBodyLimit) var request passwordResetResolveRequest - decoder := json.NewDecoder(c.Request.Body) - decoder.DisallowUnknownFields() - if err := decoder.Decode(&request); err != nil { + if err := bindJSON(c, &request, passwordResetResolveBodyLimit); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "账户信息格式无效"}) return } diff --git a/backend/internal/httpapi/password_reset_account_test.go b/backend/internal/httpapi/password_reset_account_test.go new file mode 100644 index 0000000..bc4cfa8 --- /dev/null +++ b/backend/internal/httpapi/password_reset_account_test.go @@ -0,0 +1,27 @@ +package httpapi + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "git.pcux.cn/xuans/Scert/backend/internal/config" +) + +func TestPasswordResetResolutionRejectsTrailingJSON(t *testing.T) { + server := New(Dependencies{Config: config.Default(t.TempDir()), Repository: &stubDomainRepository{}}) + tests := []string{ + `{"identifier":"admin","unexpected":true}`, + `{"identifier":"admin"}{"identifier":"again"}`, + } + for _, body := range tests { + request := httptest.NewRequest(http.MethodPost, "/api/v1/password-reset/resolve", strings.NewReader(body)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + server.Handler().ServeHTTP(response, request) + if response.Code != http.StatusBadRequest { + t.Fatalf("无效密码重置请求应返回 400,实际为 %d:%s", response.Code, response.Body.String()) + } + } +}