java - 如何根据Java Spring中的当前用户角色修剪Swagger文档?

标签 java spring spring-boot swagger

我正在使用 Spring Boot 开发应用程序,并使用 Swagger 自动生成 API 文档,并且还使用 swagger-ui.html 与这些 API 进行交互。

我也启用了 Spring Security,并且我有具有不同角色的用户。不同的角色可以使用不同的 REST API。

问题:如何配置 Swagger 以尊重 Spring 的 @Secured 注释和 swagger-ui.html 显示的修剪操作,以便仅对当前用户可用的操作可用?

即想象一下以下 Controller

@RestController
@Secured(ROLE_USER)
public void SomeRestController {
  @GetMapping
  @Secured(ROLE_USER_TOP_MANAGER)
  public String getInfoForTopManager() { /*...*/ }

  @GetMapping
  @Secured(ROLE_USER_MIDDLE_MANAGER)
  public String getInfoForMiddleManager() { /*...*/ }

  @GetMapping
  public String getInfoForAnyUser() { /*...*/ }
}

Swagger 将显示 getInfoForTopManagergetInfoForMiddleManager 操作,无论当前用户角色如何。如果当前经过身份验证的用户角色是 ROLE_USER_MIDDLE_MANAGER,我只想在 Swagger 中使用 getInfoForMiddleManagergetInfoForAnyUser 操作。

最佳答案

好的,我认为这个问题找到了很好的解决方案。解决方案由两部分组成:

  1. 通过 OperationBuilderPlugin 扩展 Controller 扫描逻辑,以保留 Swagger 供应商扩展中的角色
  2. 重写 ServiceModelToSwagger2MapperImpl bean 以根据当前安全上下文过滤掉操作

在您的项目中,这可能看起来有点不同(即您很可能没有像 securityContextResolver 这样的东西),但我相信您将从以下代码中了解此解决方案的要点:

第 1 部分:扩展 Controller 扫描逻辑以保留 Swagger 供应商扩展中的角色

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public class OperationBuilderPluginSecuredAware implements OperationBuilderPlugin {
    @Override
    public void apply(OperationContext context) {
        Set<String> roles = new HashSet<>();
        Secured controllerAnnotation = context.findControllerAnnotation(Secured.class).orNull();
        if (controllerAnnotation != null) {
            roles.addAll(List.of(controllerAnnotation.value()));
        }

        Secured methodAnnotation = context.findAnnotation(Secured.class).orNull();
        if (methodAnnotation != null) {
            roles.addAll(List.of(methodAnnotation.value()));
        }

        if (!roles.isEmpty()) {
            context.operationBuilder().extensions(List.of(new TrimToRoles(roles.toArray(new String[0]))));
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }
}

第 2 部分:根据当前安全上下文过滤操作

@Primary
@Component
public class ServiceModelToSwagger2MapperImplEx extends ServiceModelToSwagger2MapperImpl {
    @Autowired
    private SecurityContextResolver<User> securityContextResolver;

    @Override
    protected io.swagger.models.Operation mapOperation(Operation from) {
        if (from == null) {
            return null;
        }
        if (!isPermittedForCurrentUser(findTrimToRolesExtension(from.getVendorExtensions()))) {
            return null;
        }
        return super.mapOperation(from);
    }

    private boolean isPermittedForCurrentUser(TrimToRoles trimToRoles) {
        if (trimToRoles == null) {
            return true;
        }
        if (securityContextResolver.hasAnyRole(trimToRoles.getValue())) {
            return true;
        }
        return false;
    }

    private TrimToRoles findTrimToRolesExtension(@SuppressWarnings("rawtypes") List<VendorExtension> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.stream().filter(x -> x instanceof TrimToRoles).map(TrimToRoles.class::cast).findFirst()
                .orElse(null);
    }

    @Override
    protected Map<String, Path> mapApiListings(Multimap<String, ApiListing> apiListings) {
        Map<String, Path> paths = super.mapApiListings(apiListings);
        return paths.entrySet().stream().filter(x -> !x.getValue().isEmpty())
                .collect(Collectors.toMap(x -> x.getKey(), v -> v.getValue()));
    }

    @Override
    public Swagger mapDocumentation(Documentation from) {
        Swagger ret = super.mapDocumentation(from);
        Predicate<? super Tag> hasAtLeastOneOperation = tag -> ret.getPaths().values().stream()
                .anyMatch(x -> x.getOperations().stream().anyMatch(y -> y.getTags().contains(tag.getName())));
        ret.setTags(ret.getTags().stream().filter(hasAtLeastOneOperation).collect(Collectors.toList()));
        return ret;
    }
}

附:这些 impl 效率不高,但考虑到它们的使用场景,我更喜欢简单的 impl

关于java - 如何根据Java Spring中的当前用户角色修剪Swagger文档?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61859801/

相关文章:

java - 在 spring-boot 中将 protobuf 作为 JSON 发送

java - 在 Windows Server 2016/AWS 上运行的 Springboot Web 服务器无法正常工作

java - Mockito 问题 - Stubber 中的 when(java.lang.Void) 无法应用于 void

java - Spring 执行器 : Error configuring CloudFoundryActuator with multiple custom RestTemplateBuilder beans

java - 与 JAXB 的多态 XML 绑定(bind)

spring - @RequestMapping 在 tomcat 中不起作用

带有 JNDI 数据源的 Spring Boot

java - Spring:引用 resources/static 文件夹

java - 从任何地方使用Java Runtime执行cl命令

Java Applet Windows-MY keystore PrivateKey getEncoded 为 null