英文:
Golang Parsing Request body throws, contains unknown field "password"?
问题
我不确定是什么原因导致这个问题,但它抛出异常的原因是当我调用dec.DisallowUnknownFields()
时,它会认为密码不是User结构体/请求体中的一个字段。尽管密码的JSON值是“-”。所以我最初想将JSON中的字段名改为“password”,但是这会抛出一个错误:
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)
}
User.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"
英文:
I'm not sure what is causing this, but the reason it throws it is when I call dec.DisallowUnknownFields()
it somehow thinks that password isn't a field in the User struct/request body. Albeit, the password JSON is "-". So I thought originally to change the JSON to "password" for struct field Password but that throws an
internal server error: illegal base64 data at input byte 4
Request Body in Postman
{
"firstname": "George",
"lastname": "Costanza",
"email": "george@gmail.com",
"phone": "703-123-4567",
"password": "abc12"
}
I also tried to change the type of password to string, which also didn't work but arguably would be the wrong solution because we should store passwords as hashes in the DB. So, at this point I've run out of places to turn to as to why this is happening...
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)
}
User.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
}
When I run the request to sign up a user in Postman it throws the message
Request body contains unknown field "password"
答案1
得分: 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中传入的数据。所以对我来说,你真正的问题是你将两种不同类型的数据——用户提供的密码和数据库中的哈希表示——尝试混合到同一个数据空间中,这完全没有意义。毕竟,如果你要设置密码,当前的哈希(如果有)是无关紧要的,如果你要检查密码,你应该检查用户输入的内容。
这是我更喜欢的解决方案:
type User struct {
... ... ...
HashedPassword []byte `json:"-"`
Password string `json:"password"`
}
英文:
As folks already linked extensively, since JSON cannot contain bytes directly, the Go Json parser expects any []byte
objects to be stored in Base64 in the input data.
Your problem, therefore, is not necessarily on the Go end. Your problem is a mismatch with what you told Go to expect - []byte
, Base64 encoded - and what you told the client to send - "abc12" - which is not a valid base64 value.
One simple solution for your stated case would be to send a base64 encoded version of the same string:
{
"firstname": "George",
"lastname": "Costanza",
"email": "george@gmail.com",
"phone": "703-123-4567",
"password": "YWJjMTIK"
}
But I'd like to dig just a little bit deeper.
> string [...] arguably would be the wrong solution because we should store passwords as hashes in the DB
You might store passwords hashed into bytes in the database, but that's not what's coming int from curl . So to me, your real problem is that you took 2 different kinds of data - user provided password and DB hashed representation - and tried to mash them into the same data space, which has absolutely no value. After all, if you were setting a password, the current hash, if any, is irrelevant, and if you're checking the password, you'd be checking what they inputted.
This is the solution I'd prefer:
type User struct {
... ... ...
HashedPassword []byte `json:"-"`
Password string `json:"password"`
}
答案2
得分: 0
一个简单的解决方案是:
var newUser struct {
models.User
Password string `json:"password"`
}
// 解析表单数据
// err := json.NewDecoder(r.Body).Decode(&newUser)
err := app.decodeJSONBody(w, r, &newUser)
这段代码定义了一个名为newUser
的结构体变量,其中包含了models.User
的字段和一个名为Password
的字段。Password
字段使用json:"password"
的标签指定了在JSON中的键名为password
。
接下来的代码通过调用app.decodeJSONBody
函数来解析请求的JSON数据,并将解析结果存储到newUser
变量中。
英文:
A simple solution would be
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论