【问题标题】:Use JsonView to selectively hide fields使用 JsonView 选择性地隐藏字段
【发布时间】:2020-03-31 17:45:34
【问题描述】:

我正在尝试选择性地隐藏响应中的某些字段,具体取决于请求它的用户的角色。据我所知,JsonView 来自Jackson 可能是要走的路。

我需要能够显示所有字段,除非它们被标记为特定的访问级别。我为访问级别创建了以下结构:

(快速说明:我已经将基本用户留在了那里,但这并不重要)

public class View {
    public static final Map<Role, Class> MAPPING = new HashMap<>();

    static {
        MAPPING.put(Role.ROLE_ADMIN, Admin.class);
        MAPPING.put(Role.ROLE_USER, AuthenticatedUser.class);
    }

    public interface User {}
    public interface AuthenticatedUser extends User {}
    public interface Admin extends AuthenticatedUser {}
}

并按如下方式使用它们:

@Entity
public class SomeEntity {
    @Id
    @GeneratedValue
    private Integer id;

    private String baseInfo;

    @JsonView(View.AuthenticatedUser.class)
    private String userInfo;

    @JsonView(View.Admin.class)
    private String secretInfo;

    ...
}

(PS:我已经剥离了所有非必要的注释等)

现在,我希望根据访问级别,响应如下:

  1. 未经身份验证的用户:
    {
        "id": 1,
        "baseInfo": "Some basic info"
    } 
    
  2. 经过身份验证的用户:
    {
        "id": 1,
        "baseInfo": "Some basic info",
        "userInfo": "Info only the user should be able to see"
    } 
    
  3. 管理员用户:
    {
        "id": 1,
        "baseInfo": "Some basic info",
        "userInfo": "Info only the user should be able to see",
        "secretInfo": "Info only the admin can see"
    } 
    

我使用this教程中的代码将它集成到spring security和我自己的结构中。

@RestControllerAdvice
class SecurityJsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {

    @Override
    protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
        if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) {
            Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
            List<Role> foundRoles = authorities.stream()
                    .map(GrantedAuthority::getAuthority)
                    .map(Role::get).collect(Collectors.toList());

            if (foundRoles.contains(null)) {
                System.err.println("User has no auth. Setting no serialization view");
                return;
            }

            List<Class> jsonViews = foundRoles.stream().map(View.MAPPING::get)
                    .collect(Collectors.toList());
            if (jsonViews.size() == 1) {
                System.err.println("Setting " + jsonViews.get(0) + " as serialization view");
                bodyContainer.setSerializationView(jsonViews.get(0));
                return;
            }
            throw new IllegalArgumentException("Ambiguous @JsonView declaration for roles "
                    + authorities.stream()
                    .map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")));
        }
        System.err.println("No auth found");
    }
}

(请原谅糟糕的代码,我一直在尝试所有的东西......)

在这一点上,我希望结果与我上面所说的一样,但我继续获取所有字段,没有任何过滤。没有任何类型的注释可以避免字段被序列化。我知道我可以将默认设置为排除所有字段,但我注释的字段除外,但这意味着注释所有字段并且项目很大。

我对@9​​87654330@ 的最初假设是错误的,还是我在某个地方犯了错误?

提前致谢

【问题讨论】:

  • 我尝试了您的代码,但得到了不同的结果。我得到的不是所有字段,而是一个空的json消息{}作为响应(如果角色是User)。如果角色匹配,则只有那些带有 @JsonView 注释的字段才会被序列化。也许您从示例代码中删除了一个重要的注释?
  • 你是绝对正确的。我刚刚创建了另一个项目,它按您所说的那样工作,这意味着没有返回任何字段。您需要将spring.jackson.mapper.default_view_inclusion=true 属性设置为具有所有字段。通过这个项目,我得到了预期的结果,所以我猜问题出在我的代码中的其他地方。感谢您的帮助!

标签: java spring-boot spring-security jackson


【解决方案1】:

感谢@second 的评论,我已经能够找出问题所在。事实证明我犯了两个小错误:

1。将 Jackson 设置为默认包含所有属性

第一个更改是在您的application.properties 文件中将spring.jackson.mapper.default_view_inclusion 设置为true。这样,Jackson 将默认包含所有属性,消除有效负载为空的问题

2。使用“基础”用户

每当未经身份验证的用户尝试访问系统时,我们需要将序列化视图设置为基本用户。在我上面的帖子中,我有 User 视图,但我从未使用过它。将此设置为默认序列化视图(在上面的拦截器中)可以解决问题,结果就是我上面所说的。

编辑

我上传了一个示例here。它非常简单,但它显示了如何排除不同的字段。像这样使用:

  • http://localhost:8080/test
    {
        "id":1,
        "baseInfo":"This is the base info"
    }
    
  • http://localhost:8080/test?view=user
    {
        "id":1,
        "baseInfo":"This is the base info",
        "userInfo":"This is the user info"
    }
    
  • http://localhost:8080/test?view=admin
    {
        "id":1,
        "baseInfo":"This is the base info",
        "userInfo": "This is the user info",
        "secretInfo":"This is the secret info"
    }
    

希望这会有所帮助!

【讨论】:

  • 我试图验证这一点,但我仍然在这里遗漏了一些东西。我尝试为配置提供来自此anwser 的代码,而不是使用属性文件。我还在拦截器的开头添加了一行bodyContainer.setSerializationView(View.User.class);,但我仍然没有得到未注释的字段。
  • 嘿,看看我添加的例子。我删除了弹簧安全性,但基础是相同的
  • 感谢 github 项目上传。我会确认你的工作正常,但我仍然没有发现我的项目有什么不同。我会说它不接default_view_inclusion,但我不知道为什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
相关资源
最近更新 更多