【问题标题】:Spring REST: Show certain data of a User only if requester is a friendSpring REST:仅当请求者是朋友时才显示用户的某些数据
【发布时间】:2021-07-30 03:36:00
【问题描述】:

场景: 一个社区 web 应用程序,人们可以在其中使用 Spring REST 后端就某些主题形成社区。​​p>

今天我想知道如何实现类似于“谁可以看到您的电子邮件地址”的设置。 当用户进入社区和 REST 调用时,例如/api/community/1/users 正在制作中,如果对后端进行 API 调用的用户 A 不是朋友/不履行,如何阻止 CrudRepository 序列化用户 B 的电子邮件等字段用户 B 设置的某些标准,例如仅向已批准的联系人显示电子邮件。生成的 JSON 应该包含一个用户列表,其中一些具有电子邮件字段,而另一些则没有。

在搜索时,我找不到任何与我的问题相匹配的内容。以下是我发现的一些东西,但觉得它们没有多大帮助。

  1. 使用@PreAuthorize 注释控制器方法/存储库方法,传入主体。 为什么我认为这可能无济于事:如果我想根据他们的 ID 阻止某人完全查看资源,这似乎是一个很好的解决方案。例如用户只能看到自己的数据,而不能看到其他人,因为 Principal ID 与请求的资源 ID 不匹配。

  2. 按照此处所述使用 JsonFilter:https://www.baeldung.com/jackson-serialize-field-custom-criteria 使用这种方法,我看不到一种检查 WHO 是否正在提出请求的方法,例如我的电子邮件。 这种方法似乎非常适合诸如设置布尔标志以显示或不显示电子邮件的场景,适用于所有情况和任何请求者。

  3. 创建一个新的域对象,例如扩展“用户”的“朋友”,它只用于覆盖用户的@JsonIgnore 属性。虽然普通用户不会因为@JsonIgnore 而序列化他们的电子邮件字段,但朋友会通过电子邮件设置@JsonIgnore(false)。 我不喜欢这种方法,因为我觉得必须以某种方式实现此功能,而无需创建新类来覆盖 Jackson 注释。

抱歉,如果没有任何代码可显示。到目前为止,当我看到存储库公开了所有内容时,我只创建了简单的实体,并且主要是在研究如何实现上述目标。我通常更喜欢前端的事情,但出于私人和专业的原因,我也想学习 Spring 的后端。我希望这个问题不是太基本。

提前谢谢你。

【问题讨论】:

  • 也许您想研究 Springs 的基于表达式的访问控制并限制您公开的数据:docs.spring.io/spring-security/site/docs/5.4.6/reference/html5/…
  • 谢谢,这正是我目前正在查看的内容。但是,据我了解,它只允许完全过滤结果集中的某些结果,而不是过滤结果中对象的某些字段。我刚刚想到的另一个解决方案可能是使用 DTO。服务会获取所有用户,然后在从每个用户实体中创建 DTO 时,它会根据实体好友列表中是否存在具有请求者姓名的用户来填充 DTO 的电子邮件字段?

标签: spring-boot rest spring-security spring-data


【解决方案1】:

可能不是最简单的实现方式。但也许它会帮助你分解问题并找到合适的解决方案。

我假设您只想清除 API 级别的字段,但仍将其填充到您的对象中。

让我们定义一个带有一些安全元数据的模型:

class UserDTO {
   Long id;
   String name;
   @AllowOnly("hasRole('FRIEND')") // SPeL/any your custom defined language, or simpler:
 //@AllowOnly(Role.FRIEND)
   String email;
}

然后定义一个控制器类

@RestController
class SomeController {

@GetMapping("/api/community/{id}/users")
public List<UserDTO> getUsers() {
   return List.of(
      new UserDTO(1, "Bob", "email-you@gonna.see"), 
      new UserDTO(2, "Jack", "email-you-NOT@gonna.see")) 
}

}

所以我建议创建一个方面,它将根据您的权限模型清除字段。

@AfterReturning("within(@org.springframework.web.bind.annotation.RestController *) 
    && execution(* *(..))", returning="objectToClear")
public void applyFieldPermissions(Object objectToClear) {
   // Here i would parse annotations on object fields
   // and if found @AllowOnly, check your role to a user.
   // and clean up field, if necessary
}

方面的逻辑完全取决于你的情况,但对于这个简单的例子,只需要实现一些方法来检查你对特定对象的角色

boolean hasRoleOn(UserDto dto, Role role, Authentication currentUser)

【讨论】:

    【解决方案2】:

    您可以使用来自 Jackson 的 @JsonView

    首先,使用您要返回的字段创建一个 DTO,并使用 @JsonView 对其进行注释:

    public class UserDto {
      @JsonView(NoFriend.class)
      private String name;
    
      @JsonView(Friend.class);
      private String email;
    
      public static class NoFriend {}
    
      public static class Friend extends NoFriend {}
    }
    

    NoFriendFriend 内部类只是定义在什么情况下应该返回哪些字段的标记。

    现在在您的控制器中,您将 UserDto 包装在 MappingJacksonValue 中,而不是返回 UserDto

    public class UserController {
    
      @GetMapping("/api/community/1/users")
      public List<MappingJacksonValue> getUsers(@AuthenticationPrincipal Principal principal) {
    
    
        List<User> users = service.getUsers();
        return users.stream()
            .map( user -> {
                 MappingJacksonValue value = new MappingJacksonValue(UserDto.fromUser(user));
                 value.setSerializationView(getView(principal, user)); 
            })
            .collectors(toList());
    }
    
    private Class getView(Principal princapl, User user) {
        // return UserDto.Friend.class or UserDto.NoFriend.class, depending the relation of the authentication principal with the user
    }
    

    【讨论】:

    • 这看起来是一种非常简单的方法,可以完全按照我的意愿行事。 Jsonview 从未出现在我的搜索中。明天我会先试试这个。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    相关资源
    最近更新 更多