当前位置: 首页 > 知识库问答 >
问题:

JAX-RS 基于安全角色的 (@RolesAllowed) 实体过滤

欧阳绪
2023-03-14

在 JAX-RS 应用程序中,必须根据已登录用户分配到的角色来筛选我的某些资源。我正在尝试使用安全注释(@RolesAllowed、@DenyAll@PermitAll)来实现此目的。

public class MyEntity {

  public String getPublicString() {
    ...
  }

  @RolesAllowed("secretRole")
  public String getSecretString() {
    ...
  }
}

@Path("/myResource")
public MyResource {
  @GET @Path("/{id}")
  public MyEntity get(@PathParam("id") int id) {
    ...
  } 
}

现在,每个人(匿名和登录用户)都可以获取我的资源并检索MyEntity(每个id),但对于角色secretRole中的用户,我希望看到输出(此处序列化为JSON):

{
  "publicString": "...",
  "secretString": "..."
}

其他用户(无论是匿名用户还是非角色secretRole的其他用户)应该只看到:

{
  "publicString": "..."
}

我知道Jersey有实体过滤(以及基于安全角色进行过滤的实现)。

不幸的是,Liberty(基于Apache CXF)没有这样的特性。

由于我的解决方案主要处理JSON -使用Jackson - 我基于Jackson的< code > bean serializer modifier 做了一些工作。忘记< code > bean serializer modifier :对于每种bean类型,它只被调用一次(因此第一个用户定义了为所有其他用户序列化哪些属性——不,谢谢)。

刚刚发现了另一个杰克逊概念,每次豆子即将被序列化时都会应用:属性筛选Json筛选

它的工作原理,实现非常简单:

new SimpleBeanPropertyFilter() {
  @Override
  protected boolean include(BeanPropertyWriter writer) {
    return include((PropertyWriter)writer);
  }

  @Override
  protected boolean include(PropertyWriter writer) {
    if (writer.findAnnotation(DenyAll.class) != null) {
      return false;
    }

    RolesAllowed rolesAllowed = writer.findAnnotation(RolesAllowed.class);
    if (rolesAllowed != null) {
      boolean anyMatch = Arrays.stream(rolesAllowed.value())
        .anyMatch(role -> securityContext.isUserInRole(role));

      if (!anyMatch) {
        return false;
      }
    }

    return true;
  }
}

上述实现的致命弱点是securityContext引用(需要一个securityContext实例)。

我找不到方法来获取对当前安全上下文的引用。

通常,安全上下文@Context注入 - 作为方法参数或字段参数。这些都不适用于 Bean 序列化程序修改器

我成功地注入了@Context.SecurityContext(通过字段或构造函数参数);它恰好是Liberty中的一个<code>ThreadLocalSecurityContext</code>。但是它的方法<code>isUserInRole

新泽西不是我的选择。我被绑定到Liberty(基于Apache CXF)。

我已经习惯了杰克逊,但这不是必须的。JSON和REST是。

< s >等等:我以为问题出在< code>securityContext上,但也许它不是罪魁祸首。及时:我已经设法注入了< code > @ Context security Context (既通过字段也通过构造函数参数);它恰好是一个< code > ThreadLocalSecurityContext ,所以我认为它将从threadlocal存储中获取实际的主体。

但是现在我意识到 BeanSerializer修改器#change属性只被调用一次(对于每个bean),然后更改的属性列表被重用!我会仔细研究杰克逊的规格;也许我会切换到JSON-B,正如@Andy麦克赖特所指出的那样(如果它 的属性可见性策略没有缓存结果)。

以前使用 Bean 序列化程序修改器的实现:

public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
  return beanProperties.stream()
    .filter(property -> {
      if (property.findAnnotation(DenyAll.class) != null) {
        return false;
      }

      RolesAllowed rolesAllowed = property.findAnnotation(RolesAllowed.class);
      if (rolesAllowed != null) {
        boolean anyMatch = Arrays.stream(rolesAllowed.value())
          .anyMatch(role -> securityContext.isUserInRole(role));
        if (!anyMatch) {
          return false;
        }
      }

      return true;
    })
    .collect(toList());
}

共有1个答案

百里雅珺
2023-03-14

我已经通过ThreadLocal处理了SecurityContext。为此,我实现了一个ContainerRequestFilter

  static final ThreadLocal<SecurityContext> tlSecurityContext = new ThreadLocal<>();

  @Provider
  public static class SecurityContextSavingRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
      tlSecurityContext.set(requestContext.getSecurityContext());
    }

  }

然后tlSecurityContext.get()可以用作当前SecurityContext

然而,我不知道这是否无效,或者JAX-遥感规范是否不推荐这样做。

除此之外,我还切换到JSON-B(来自杰克逊),因为:

>

  • 通过特性jsonb-1.0,它与Liberty(服务器和客户端JAX-RS)有更好的集成;

    属性过滤不那么冗长(比Jackson的Property tyFilter),尽管也不那么强大。

    ContextResolver

    @Provider
    public class JsonbConfigContextResolver implements ContextResolver<Jsonb> {
    
      @Override
      public Jsonb getContext(Class<?> type) {
        return JsonbBuilder.newBuilder().withConfig(getConfig()).build();
      }
    
      private JsonbConfig getConfig() {
        return new JsonbConfig().withPropertyVisibilityStrategy(new SecurityPropertyVisibilityStrategy());
      }
    }
    

    属性可见性正确实现筛选的策略:

    public class SecurityPropertyVisibilityStrategy implements PropertyVisibilityStrategy {
      @Override
      public boolean isVisible(Field field) {
        return false;
      }
    
      @Override
      public boolean isVisible(Method method) {
        if (method.getAnnotation(DenyAll.class) != null) {
          return false;
        }
    
        RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
          boolean anyMatch = Arrays.stream(rolesAllowed.value())
              .anyMatch(role -> isUserInRole(role));
          if (!anyMatch) {
            return false;
          }
        }
    
        return true;
      }
    

    最后是ThreadLocal黑客本身:

      private boolean isUserInRole(String role) {
        return securityContext.get().isUserInRole(role);
      }
    
      private static final ThreadLocal<SecurityContext> securityContext = new ThreadLocal<>();
    
      @Provider
      public static class SecurityContextSavingRequestFilter implements ContainerRequestFilter {
        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException {
          securityContext.set(requestContext.getSecurityContext());
        }
      }
    }
    

  •  类似资料:
    • 我需要使用登录的员工的角色在jsp页面中隐藏导航。应用程序使用单点登录登录。我将只从另一个应用程序获得用户的用户名,我将使用该应用程序在数据库中搜索以获得员工的详细信息。我在employee表中有一个role列,我目前正在javascript中使用它来显示和隐藏导航。如何设置spring security role并在jsp标记中使用“hasRole('any role')”,以显示或隐藏导航。我

    • 我正在开发REST webService,我的一些客户机将使用我的webservices,所以为了识别真正的客户机,我决定给每个真正的客户机一个唯一的应用程序令牌。客户机将对这个令牌进行编码,他们将把这个令牌放在请求头中,我已经在我的REST webservices中配置了一个REST过滤器来验证令牌。我不想使用https。我的问题是,任何人都可以从我的客户端站点获取该令牌,并可以使用我的REST

    • 本文展示了如何根据不同的用户角色,在登录之后来重定向到不同的页面。 在 method-security项目的基础上,我们构建了一个role-base-login项目。 build.gradle 修改 build.gradle 文件,让我们的role-base-login项目成为一个新的项目。 修改内容也比较简单,修改项目名称及版本即可。 jar { baseName = 'role-bas

    • 我添加了基于方法的安全性和角色层次结构。我在构建过程中不断遇到以下异常: 组织。springframework。豆。BeanInstationException:未能实例化[org.springframework.web.servlet.HandlerMapping]:工厂方法“defaultServletHandlerMapping”引发异常;嵌套的例外是java。lang.IllegalArg

    • 问题内容: 使用RESTEasy和Jackson,是否可以在模型中使用注释,从而避免根据用户的角色在输出中序列化某些属性? 我已经找到了大量关于如何使用Jersey的文档,但是关于RESTEasy却没有。 我在此架构上受阻,因此切换库不是一个选择,并且也不像此处说明的那样使用自定义,因为该模型足够大,以至于标记大型数据集的每个属性都太耗时用于正确的序列化。另外,这是指Jackson库的较旧版本,我

    • 我有一个带有4个微服务、eureka服务器和一个集中式API网关的Spring Boot应用程序。 所有外部流量都通过API网关进入我的微服务。 我的API网关(Zuul)正在验证和验证JWT令牌。 JWT令牌由我的一个微服务在用户登录后生成(用户微服务),该令牌包含用户Id和他的角色/权限。 现在我想在网关以外的微服务中存在的方法上实现基于角色的安全性。 我尝试过使用,但它在网关外不起作用(显然