【问题标题】:Golang Parsing Request body throws, contains unknown field "password"?Golang解析请求正文抛出,包含未知字段“密码”?
【发布时间】:2021-11-28 14:41:31
【问题描述】:

我不确定是什么原因造成的,但它抛出的原因是当我调用dec.DisallowUnknownFields() 时,它以某种方式认为密码不是用户结构/请求正文中的字段。尽管密码 JSON 是“-”。所以我最初想将结构字段密码的 JSON 更改为“密码”,但这会引发

internal server error:  illegal base64 data at input byte 4

Postman 中的请求正文

{
    "firstname": "George",
    "lastname": "Costanza",
    "email": "george@gmail.com",
    "phone": "703-123-4567",
    "password": "abc12"
}

我还尝试将密码类型更改为字符串,这也不起作用,但可以说是错误的解决方案,因为我们应该将密码作为哈希值存储在数据库中。所以,在这一点上,我已经没有地方可以转向为什么会发生这种情况了......

models.go

type User struct {
    ID primitive.ObjectID    `json:"_id,omitempty" bson:"_id,omitempty"`
    FirstName      string    `json:"firstname"`
    LastName       string    `json:"lastname"`
    Email          string    `json:"email"`
    Phone          string    `json:"phone"`
    Password       []byte    `json:"-"`
    Created        time.Time `json:"-"`
    Active         bool      `json:"-"`
    Address        Address   `json:"address,omitempty"`
}

Helpers.go

type malformedRequest struct {
    status int
    msg    string
}

func (mr *malformedRequest) Error() string {
    return mr.msg
}

func (app *appInjection) decodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error {
    if r.Header.Get("Content-Type") != "" {
        value, _ := header.ParseValueAndParams(r.Header, "Content-Type")
        if value != "application/json" {
            msg := "Content-Type header is not application/json"
            return &malformedRequest{status: http.StatusUnsupportedMediaType, msg: msg}
        }
    }

    r.Body = http.MaxBytesReader(w, r.Body, 1048576)

    dec := json.NewDecoder(r.Body)
    dec.DisallowUnknownFields()
    err := dec.Decode(&dst)

    if err != nil {
        var syntaxError *json.SyntaxError
        var unmarshalTypeError *json.UnmarshalTypeError

        switch {
        case errors.As(err, &syntaxError):
            msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset)
            return &malformedRequest{status: http.StatusBadRequest, msg: msg}

        case errors.Is(err, io.ErrUnexpectedEOF):
            msg := fmt.Sprintf("Request body contains badly-formed JSON")
            return &malformedRequest{status: http.StatusBadRequest, msg: msg}

        case errors.As(err, &unmarshalTypeError):
            msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset)
            return &malformedRequest{status: http.StatusBadRequest, msg: msg}

        case strings.HasPrefix(err.Error(), "json: unknown field "):
            fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
            msg := fmt.Sprintf("Request body contains unknown field %s", fieldName)
            return &malformedRequest{status: http.StatusBadRequest, msg: msg}

        case errors.Is(err, io.EOF):
            msg := "Request body must not be empty"
            return &malformedRequest{status: http.StatusBadRequest, msg: msg}

        case err.Error() == "http: request body too large":
            msg := "Request body must not be larger than 1MB"
            return &malformedRequest{status: http.StatusRequestEntityTooLarge, msg: msg}

        default:
            return err
        }
    }

    err = dec.Decode(&struct{}{})
    if err != io.EOF {
        msg := "Request body must only contain a single JSON object"
        return &malformedRequest{status: http.StatusBadRequest, msg: msg}
    }

    return nil
}

Handlers.go

func (app *appInjection) RegisterUser(w http.ResponseWriter, r *http.Request) {

    // Guide addressing headers, syntax error's, and preventing extra data fields
    // https://www.alexedwards.net/blog/how-to-properly-parse-a-json-request-body
    w.Header().Set("Content-Type", "application/json")
    var newUser models.User
    //Parse the form data
    //err := json.NewDecoder(r.Body).Decode(&newUser)
    err := app.decodeJSONBody(w, r, &newUser)
    if err != nil {
        var mr *malformedRequest
        if errors.As(err, &mr) {
            http.Error(w, mr.msg, mr.status)
            //app.clientError(w, mr.status)
        } else {
            log.Println(err.Error())
            //app.clientError(w, http.StatusInternalServerError)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
        return
    }

    //TODO: Validate the form
    //If there is no error and the form is validated, create a new user from http request
    //Insert the new user into the database
    uid, _ := app.user.Insert(
        newUser.FirstName,
        newUser.LastName,
        newUser.Email,
        newUser.Phone,
        string(newUser.Password))

    json.NewEncoder(w).Encode("Record Inserted")
    json.NewEncoder(w).Encode(uid)
}

用户.go

func (u *UserFunctions) Insert(firstname, lastname, email, phone, password string) (primitive.ObjectID, error) {
    //Insert user to the database
    userCollection := u.CLIENT.Database("queue").Collection("users")
    var user models.User

    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        object, _ := primitive.ObjectIDFromHex("")
        return object, err
    }
    user.FirstName = firstname
    user.LastName = lastname
    user.Email = email
    user.Phone = phone
    user.Password = hashedPassword
    user.Created = time.Now().UTC()
    user.Active = true

    //Insert the user into the database
    result, err := userCollection.InsertOne(context.TODO(), user)
    if err != nil {
        fmt.Println(err)
    }

    //Check ID of the inserted document
    insertedID := result.InsertedID.(primitive.ObjectID)
    //fmt.Println(insertedID)

    return insertedID, nil
}

当我在 Postman 中运行请求以注册用户时,它会抛出消息

Request body contains unknown field "password"

【问题讨论】:

  • DisallowUnknownFields 导致解码器在目标是结构并且输入包含与目标中任何未忽略的导出字段不匹配的对象键时返回错误。字段标签json:"-" 指定忽略该字段。
  • 如果我将“-”更改为“密码”,则会引发“内部服务器错误:输入字节 4 处的 base64 数据非法”
  • @PatrickCockrill: 因为[]byte 必须在 json 中进行 base64 编码。
  • «输入字节 4 处的非法 base64 数据»——这完全是另一回事。在您急于就 SO 提出另一个问题之前,请花点时间阅读golang.org/pkg/encoding/json,其中包含这两个问题的答案。谢谢!
  • base64

标签: json api go postman


【解决方案1】:

正如人们已经广泛链接的那样,由于 JSON 不能直接包含字节,Go Json 解析器期望任何 []byte 对象都以 Base64 格式存储在输入数据中。

因此,您的问题不一定在 Go 端。您的问题与您告诉 Go 期望的内容不匹配 - []byte,Base64 编码 - 以及您告诉客户端发送的内容 - “abc12” - 这不是有效的 base64 值。

针对您所述情况的一个简单解决方案是发送相同字符串的 base64 编码版本:

{
    "firstname": "George",
    "lastname": "Costanza",
    "email": "george@gmail.com",
    "phone": "703-123-4567",
    "password": "YWJjMTIK"
}

但我想再深入一点。

string [...] 可能是错误的解决方案,因为我们应该将密码作为哈希值存储在数据库中

您可以将散列成字节的密码存储在 数据库 中,但这不是来自 curl 的 int 内容。所以对我来说,您的真正问题是您获取了 2 种不同类型的数据——用户提供的密码和 DB 哈希表示——并试图将它们混合到同一个数据空间中,这绝对没有任何价值。毕竟,如果你设置了密码,那么当前的哈希值(如果有的话)是无关紧要的,如果你在检查密码,你就是在检查他们输入的内容。

这是我更喜欢的解决方案:

type User struct {
    ... ... ...
    HashedPassword       []byte    `json:"-"`
    Password string `json:"password"`

}

【讨论】:

    【解决方案2】:

    一个简单的解决方案是

    var newUser struct {
        models.User
        Password string `json:"password"`
    }
    //Parse the form data
    //err := json.NewDecoder(r.Body).Decode(&newUser)
    err := app.decodeJSONBody(w, r, &newUser)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-28
      • 2017-08-06
      • 2019-08-13
      • 2018-07-02
      • 2016-04-08
      • 1970-01-01
      相关资源
      最近更新 更多