springdoc-openapi 应用默认全局 SecurityScheme 可能吗?

2022-01-22 00:00:00 swagger spring java openapi springdoc

我有以下 SecurityScheme 使用 springdoc-openapi 为 java SpringBoot RESTful 应用定义:

 @Bean公共 OpenAPI customOpenAPI() {返回新的 OpenAPI().components(new Components().addSecuritySchemes("bearer-jwt",新的 SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT").in(SecurityScheme.In.HEADER).name("授权"))).info(new Info().title("App API").version("snapshot"));}

是否可以将其全局应用于所有路径,而无需添加 @SecurityRequirement 注释到 @Operation 代码中到处都是注解?

如果是,如何为不安全的路径添加排除项?

解决方案

是的,你可以在同一个地方调用addSecurityItem:

 @Bean公共 OpenAPI customOpenAPI() {返回新的 OpenAPI().components(new Components().addSecuritySchemes("bearer-jwt",new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT").in(SecurityScheme.In.HEADER).name(授权"))).info(new Info().title("App API").version("snapshot")).addSecurityItem(new SecurityRequirement().addList(bearer-jwt", Arrays.asList(read", write")));}

全局安全模式可以被具有 @SecurityRequirements 注释的不同模式覆盖.包括删除操作的安全模式.例如,我们可以删除注册路径的安全性.

@SecurityRequirements@PostMapping("/registration")public ResponseEntity post(@RequestBody @Valid Registration: 注册) {返回registrationService.register(注册);}

同时仍保留其他 API 的安全架构.

旧答案(2019 年 12 月 20 日):

全局安全模式可以被具有 @SecurityRequirements 注释的不同模式覆盖.但对于不安全的路径,它不能被删除.springdoc-openapi 中确实缺少功能,OpenAPI 标准允许这样做.请参阅为特定操作禁用全局安全

虽然有一个解决方法.springdoc-openapi 有一个 OpenApiCustomiser 的概念,可以用来拦截生成的模式.在定制器内部,可以以编程方式修改操作.要删除任何继承的安全性,需要将字段 security 设置为空数组.逻辑可以基于任意规则,例如操作名称.我使用了标签.

定制器:

import io.swagger.v3.oas.models.OpenAPI;导入 io.swagger.v3.oas.models.Operation;导入 io.swagger.v3.oas.models.PathItem;导入 org.springdoc.api.OpenApiCustomiser;导入 org.springframework.stereotype.Component;导入 javax.validation.constraints.NotNull;导入 java.util.Arrays;导入 java.util.Collections;导入 java.util.List;导入 java.util.Objects;导入 java.util.function.Function;导入 java.util.stream.Collectors;导入 java.util.stream.Stream;@零件公共类 SecurityOverrideCustomizer 实现 OpenApiCustomiser {公共静态最终字符串 UNSECURED = "security.open";私有静态最终列表>OPERATION_GETTERS = Arrays.asList(PathItem::getGet, PathItem::getPost, PathItem::getDelete, PathItem::getHead,PathItem::getOptions, PathItem::getPatch, PathItem::getPut);@覆盖公共无效定制(OpenAPI openApi){openApi.getPaths().forEach((path, item) -> getOperations(item).forEach(操作 -> {列表<字符串>标签 = operation.getTags();if (tags != null && tags.contains(UNSECURED)) {operation.setSecurity(Collections.emptyList());operation.setTags(filterTags(tags));}}));}私有静态流<操作>getOperations(PathItem pathItem) {返回 OPERATION_GETTERS.stream().map(getter -> getter.apply(pathItem)).filter(Objects::nonNull);}私有静态列表<字符串>filterTags(列出<字符串>标签){返回标签.stream().filter(t -> !t.equals(UNSECURED)).collect(Collectors.toList());}}

现在我们可以将 @Tag(name = SecurityOverrideCustomizer.UNSECURED) 添加到不安全的方法中:

 @Tag(name = SecurityOverrideCustomizer.UNSECURED)@GetMapping("/open")@ResponseBody公共字符串打开(){返回它有效!";}

请记住,这只是一种解决方法.希望该问题将在下一个 springdoc-openapi 版本中得到解决(在撰写本文时,当前版本为 1.2.18).

有关工作示例,请参阅 springdoc-security-override-fixp>

I have the following SecurityScheme definition using springdoc-openapi for java SpringBoot RESTful app:

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .components(new Components().addSecuritySchemes("bearer-jwt",
                 new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
                .in(SecurityScheme.In.HEADER).name("Authorization")))
                .info(new Info().title("App API").version("snapshot"));
    }

Is it possible to apply it globally to all paths, without having to go and add @SecurityRequirement annotations to @Operation annotation everywhere in the code?

If it is, how to add exclusions to unsecured paths?

解决方案

Yes, you can do it in the same place calling addSecurityItem:

  @Bean
  public OpenAPI customOpenAPI() {
    return new OpenAPI()
            .components(new Components().addSecuritySchemes("bearer-jwt",
                new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
                    .in(SecurityScheme.In.HEADER).name("Authorization")))
            .info(new Info().title("App API").version("snapshot"))
            .addSecurityItem(
                    new SecurityRequirement().addList("bearer-jwt", Arrays.asList("read", "write")));
  }

Global security schema can be overridden by a different one with the @SecurityRequirements annotation. Including removing security schemas for an operation. For example, we can remove security for registration path.

@SecurityRequirements
@PostMapping("/registration")
public ResponseEntity post(@RequestBody @Valid Registration: registration) {
    return registrationService.register(registration);
}

While still keeping security schemas for other APIs.

Old answer (Dec 20 '19):

Global security schema can be overridden by a different one with the @SecurityRequirements annotation. but it cannot be removed for unsecured paths. It is acctualy missing fueature in the springdoc-openapi, OpenAPI standard allows it. See disable global security for particular operation

There is a workaround though. The springdoc-openapi has a concept of an OpenApiCustomiser which can be used to intercept generated schema. Inside the customizer, an operation can be modified programmatically. To remove any inherited security, the field security needs to be set to an empty array. The logic may be based on any arbitrary rules e.g operation name. I used tags.

The customizer:

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import org.springdoc.api.OpenApiCustomiser;
import org.springframework.stereotype.Component;

import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class SecurityOverrideCustomizer implements OpenApiCustomiser {

    public static final String UNSECURED = "security.open";

    private static final List<Function<PathItem, Operation>> OPERATION_GETTERS = Arrays.asList(
            PathItem::getGet, PathItem::getPost, PathItem::getDelete, PathItem::getHead,
            PathItem::getOptions, PathItem::getPatch, PathItem::getPut);

    @Override
    public void customise(OpenAPI openApi) {
        openApi.getPaths().forEach((path, item) -> getOperations(item).forEach(operation -> {
            List<String> tags = operation.getTags();
            if (tags != null && tags.contains(UNSECURED)) {
                operation.setSecurity(Collections.emptyList());
                operation.setTags(filterTags(tags));
            }
        }));
    }

    private static Stream<Operation> getOperations(PathItem pathItem) {
        return OPERATION_GETTERS.stream()
                .map(getter -> getter.apply(pathItem))
                .filter(Objects::nonNull);
    }

    private static List<String> filterTags(List<String> tags) {
        return tags.stream()
                .filter(t -> !t.equals(UNSECURED))
                .collect(Collectors.toList());
    }
}

Now we can add @Tag(name = SecurityOverrideCustomizer.UNSECURED) to unsecured methods:

    @Tag(name = SecurityOverrideCustomizer.UNSECURED)
    @GetMapping("/open")
    @ResponseBody
    public String open() {
        return "It works!";
    }

Please bear in mind that it is just a workaround. Hopefully, the issue will be resolved in the next springdoc-openapi versions (at the time of writing it the current version is 1.2.18).

For a working example see springdoc-security-override-fix

相关文章