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

查看:77
本文介绍了如何基于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将显示操作 getInfoForTopManager getInfoForMiddleManager ,无论当前用户角色如何.如果当前经过身份验证的用户角色是 ROLE_USER_MIDDLE_MANAGER ,我希望Swagger中只能使用 getInfoForMiddleManager getInfoForAnyUser 操作.

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;
    }
}

p.s.这些impls效率不高,但是鉴于其使用场景,我更喜欢简单的impl

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

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

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