序列化为JSON时,如何根据运行时条件省略某些字段?

时间:2014-04-03 11:13:36

标签: json go

在Go中实现的Web服务中,我希望能够根据用户的角色限制JSON响应中返回的字段。

例如,我可能有一个角色为guest的当前登录用户,另一个角色为admin的用户

对于管理员,我希望json拥有所有密钥,例如

{
  id: 1,
  name: "John",
  role: "admin"
}

并且客人没有角色密钥,例如

{
  id: 1,
  name: "John"
}

我现在可以封送json并返回所有字段。我需要能够限制它。

3 个答案:

答案 0 :(得分:6)

您可以查看@Volker提出的建议并清除用户没有权限的结构字段。这可能是最容易实现的。

类似的第二个选项是创建自定义JSON编码器。仅当角色struct标签与当前用户的角色匹配时才对字段进行编码的方法。这里有一些伪代码来说明:

type T struct {
    currentRole Role   `json:"-"`
    FieldA      string `json:"field_a,omitempty", role:"guest"`
    FieldB      string `json:"field_b,omitempty", role:"guest"`
    FieldC      int    `json:"field_c,omitempty", role:"admin"`
}

// Have T implement the encoding/json.Marshaler interface.
func (t *T) MarshalJSON() ([]byte, error) {
    var buf bytes.Buffer

    // Use some reflection magic to iterate over struct fields.
    for _, field := range getStructFields(t) {
        // More reflection magic to extract field tag data.
        role := getFieldTag(field, "role")

        // If the field tag's role matches our current role,
        // we are good to go. otherwise, skip this field.
        if !matchingRole(role, t.currentRole) {
            continue // skip this field 
        }

        data, err := json.Marshal(fieldValue(field))
        ...
        _, err = buf.Write(data)
        ...
    }

    return buf.Bytes(), nil
}

如果你需要新的角色,这将是一个痛苦的维持。所以这不是我会轻易考虑做的事情。

安全问题

我不完全确定您所寻找的是解决问题的正确方法。这取决于您使用代码的上下文,这在您的问题中并不明确。但是,如果这涉及一个网站,用户在网站上的能力仅由role JSON字段的值定义,那么您正在查看安全漏洞。他们可以直接进入浏览器调试器并更改此JSON对象的值以包含"role: "admin"字段。并且presto!即时行政权力。在模板处理期间,服务器是否应该真正地处理是否基于用户角色呈现页面的某些部分。就像发布到服务器的所有数据一样,应该再次检查和检查以确保它来自可信来源。

如果这些都不适用于你,那么无论如何都要忽视这一段。

答案 1 :(得分:1)

这个问题似乎很老,但我最近想做同样的事情。也许这将有助于未来的人。这是另一种方法:您可以定义自己的Marshal接口并使用匿名结构。

//User holds all variables
//even private ones
type User struct {
    ID      int64
    Name    string
    Role    string
}

//MarshalJSON gives back json user
//but only the public fields!
func (u *User) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        ID string `json:"id"`
        Name string `json:"name"`
    }{u.ID, u.Name})
}

在块中放置if u.Role == "admin"语句以决定是否编组其余语句会非常容易。

答案 2 :(得分:1)

另一个选项也可用于定义输出中的字段集,以获取来自appengine数据存储区查询的结构列表。

    // Setting different JSON output field for the same struct, using anonymous
    // fields (inspired by inspired by http://choly.ca/post/go-json-marshalling/)

    // This alternative could be used to load a resultset from an appengine datastore
    // query and returned a custom field combination for the list items.

    package main

    import (
        "encoding/json"
        "fmt"
    )

    type User struct {
        ID         string `json:"id"`
        Name       string `json:"name"`
        Role       string `json:"-"`
        LaunchCode string `json:"-"`
    }

    type AdminOutputUser User

    func (user *AdminOutputUser) MarshalJSON() ([]byte, error) {
        type Alias AdminOutputUser
        return json.Marshal(&struct {
            *Alias
            Role string `json:"role"`
        }{
            (*Alias)(user),
            user.Role,
        })
    }

    type SuperadminOutputUser User

    func (user *SuperadminOutputUser) MarshalJSON() ([]byte, error) {
        type Alias SuperadminOutputUser
        return json.Marshal(&struct {
            *Alias
            Role       string `json:"role"`
            LaunchCode string `json:"code"`
        }{
            (*Alias)(user),
            user.Role,
            user.LaunchCode,
        })
    }

    func main() {
        user := User{"007", "James Bond", "admin", "12345678"}
        adminOutput := AdminOutputUser(user)
        superadminOutput := SuperadminOutputUser(user)

        b, _ := json.Marshal(&user)
        fmt.Printf("%s\n\n", string(b))
        // {"id":"007","name":"James Bond"}

        b, _ = json.Marshal(&adminOutput)
        fmt.Printf("%s\n\n", string(b))
        // {"id":"007","name":"James Bond","role":"admin"}

        b, _ = json.Marshal(&superadminOutput)
        fmt.Printf("%s\n\n", string(b))
        // {"id":"007","name":"James Bond","role":"admin","code":"12345678"}
    }

    // for appengine could do something like
    // ...
    // var users []AdminOutputUser // or User or SuperadminOutputUser
    // q := datastore.NewQuery("User")
    // keys, err := q.GetAll(ctx, &users)
    // ...

https://play.golang.org/p/ignIz0hP0z