如何根据 Java Spring 中的当前用户角色修剪 Swagger 文档? [英] How to trim Swagger docs based on current User Role in Java Spring?

查看:24
本文介绍了如何根据 Java Spring 中的当前用户角色修剪 Swagger 文档?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm developing application using Spring Boot, and I'm using Swagger to auto-generate API docs and also I use swagger-ui.html to interact with those APIs.

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

I have Spring Security enabled too, and I have Users with different roles. Different REST APIs are available to different roles.

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

Question: how do I configure Swagger to respect Spring's @Secured annotation and trim operations displayed by swagger-ui.html so that only operations available to current user are available?

即想象下面的控制器

@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 操作.

Swagger will show both operations getInfoForTopManager and getInfoForMiddleManager regardless of current user role. In case currently authenticated user role is ROLE_USER_MIDDLE_MANAGER, I want only getInfoForMiddleManager and getInfoForAnyUser operations to be available in the Swagger.

推荐答案

好的,我认为找到了解决该问题的好方法.解决方案由两部分组成:

Ok, I think found good solution to that question. Solution consists of 2 parts:

  1. 通过 OperationBuilderPlugin 扩展控制器扫描逻辑以保留 Swagger 供应商扩展中的角色
  2. 覆盖 ServiceModelToSwagger2MapperImpl bean 以根据当前安全上下文过滤掉操作
  1. Extend controllers scanning logic through OperationBuilderPlugin to retain roles in the Swagger's vendor extensions
  2. Override ServiceModelToSwagger2MapperImpl bean to filter out actions based on current security context

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

In your project this might look a bit different (i.e. most likely you don't have thing like securityContextResolver), but I believe you'll get the gist of this solution from following code:

第 1 部分:扩展控制器扫描逻辑以保留 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

p.s. these impls are not efficient, but given their usage scenarios I preferred simple impl

这篇关于如何根据 Java Spring 中的当前用户角色修剪 Swagger 文档?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆