swagger-api / swagger-core

Examples and server integrations for generating the Swagger API Specification, which enables easy access to your REST API
http://swagger.io
Apache License 2.0
7.38k stars 2.18k forks source link

disable global security for particular operation #2844

Open jmilkiewicz opened 6 years ago

jmilkiewicz commented 6 years ago

I am using swagger.core.v3 in version 2.0.2 to generate openAPI 3.0 definition files and I am having trouble to disable "security" for a particular endpoint. I have global securitySchemes and root security element defined:

 Info info = new Info()
            .title("someTitle")
            .description("some description")
            .version("1.0")

    SecurityScheme jwtSecurity = new SecurityScheme()
            .type(SecurityScheme.Type.HTTP)
            .name("Authorization")
            .in(SecurityScheme.In.HEADER)
            .scheme("bearer")
            .bearerFormat("JWT");

    String securitySchemaName = "JWT";
    OpenAPI oas = new OpenAPI()
            .info(info)
            .components(new Components().addSecuritySchemes(securitySchemaName, jwtSecurity))
            .addSecurityItem(new SecurityRequirement().addList(securitySchemaName));

    SwaggerConfiguration oasConfig = new SwaggerConfiguration()
            .openAPI(oas)
            .prettyPrint(true)
            .resourcePackages(Stream.of("my.resources.package")
                    .collect(Collectors.toSet()));
    environment.jersey().register(new OpenApiResource()
            .openApiConfiguration(oasConfig));

And definition file is nicely generated:

{
  "openapi" : "3.0.1",
  "security" : [ {
    "JWT" : [ ]
  } ],
  "paths" : {   
    ...
  },
  "components" : {
    "schemas" : {
     ...
    },
    "securitySchemes" : {
      "JWT" : {
        "type" : "http",
        "scheme" : "bearer",
        "bearerFormat" : "JWT"
      }
    }
  }
}

According to OPEN API 3 spec https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#securityRequirementObject i shall be able to override global "security requirement" for an individual operation. I would like to "disable" JWT security for a few operations and according to https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#securityRequirementObject it can be done by

To remove a top-level security declaration, an empty array can be used.

I simply wanna specify "NO Security" for a particular opetration:

    @POST
@Operation(
        summary = "authenticate user",
        responses = {
                @ApiResponse(responseCode = "200", description = "when user is successfully authenticated",
                        content = @Content(schema = @Schema(implementation = AuthenticateUserOutput.class))),                   
                @ApiResponse(responseCode = "401", description = "when email/password not valid or user is blocked/inactive"),
        }
        ,security = what to put here ?
)

I tried security = {} or security = @SecurityRequirement(name ="") but in both cases no security element within operation is generated at all....

frantuma commented 6 years ago

at the moment you can achieve what you need using filter, as in the example below; possibly related annotation processing will be enhanced to allow for annotation only handling of such a case, but no ETA atm

    class SecurityFilter extends AbstractSpecFilter {
        @Override
        public Optional<Operation> filterOperation(Operation operation, ApiDescription api, Map<String, List<String>> params, Map<String, String> cookies, Map<String, List<String>> headers) {
            if ("security".equals(operation.getOperationId())) { // or whatever operationId..
                operation.setSecurity(new ArrayList<>());
                return Optional.of(operation);
            }
            return super.filterOperation(operation, api, params, cookies, headers);
        }
    }

in your case (usage of provided OpenApiResource) you can specify filter providing fully qualified name in config object:

        SwaggerConfiguration oasConfig = new SwaggerConfiguration()
                .openAPI(oas)
                .prettyPrint(true)
                .filterClass("fully.qualified.SecurityFilter")
                .resourcePackages(Stream.of("my.resources.package")
                        .collect(Collectors.toSet()));
mafor commented 4 years ago

Interestingly, it works on the SwaggerHub: security-override-test

ratijas commented 4 years ago

bump

sbarfurth commented 3 years ago

I found that simply adding io.swagger.v3.oas.annotations.security.SecurityRequirements as an annotation will do what you want.

@RestController
class Controller {

    @SecurityRequirements // This is it
    @GetMapping("/api/v1/endpoint")
    public String endpoint() {
        return "no security required!";
    }

}

At least for me this disabled all security requirements for the method.

Frantch commented 2 years ago

Doesn't work for me with Swagger 2.1.13

doubleaxe commented 2 years ago

To make @sbarfurth solution to work on latest Swagger, 2.1.13, you should also hack inside reader class. You also may place custom processing there, for example my security classes are annotated with @Secured annotation, and I just remove global security info for classes without this annotation.

readerClass: my.SwaggerReader
prettyPrint: true
openAPI:
  info:
    title: MY REST API

or

swaggerConfiguration.readerClass("my.SwaggerReader");

then

public class SwaggerReader extends Reader {

    @Override
    protected Operation parseMethod(Class<?> cls,
            Method method,
            List<Parameter> globalParameters,
            Produces methodProduces,
            Produces classProduces,
            Consumes methodConsumes,
            Consumes classConsumes,
            List<SecurityRequirement> classSecurityRequirements,
            Optional<ExternalDocumentation> classExternalDocs,
            Set<String> classTags,
            List<Server> classServers,
            boolean isSubresource,
            RequestBody parentRequestBody,
            ApiResponses parentResponses,
            JsonView jsonViewAnnotation,
            ApiResponse[] classResponses,
            AnnotatedMethod annotatedMethod
            ) {
        Operation parsedOperation = super.parseMethod(cls,
                method,
                globalParameters,
                methodProduces,
                classProduces,
                methodConsumes,
                classConsumes,
                classSecurityRequirements,
                classExternalDocs,
                classTags,
                classServers,
                isSubresource,
                parentRequestBody,
                parentResponses,
                jsonViewAnnotation,
                classResponses,
                annotatedMethod
                );
        SecurityRequirements security[] = method.getAnnotationsByType(SecurityRequirements.class);
        if((security != null) && (security.length == 1) &&
                ((security[0].value() == null) || (security[0].value().length == 0))) {
                parsedOperation.setSecurity(new ArrayList<>(0));
        }

        return parsedOperation;
    }
}

and now this should work

@RestController
class Controller {

    @SecurityRequirements // This is it
    @GetMapping("/api/v1/endpoint")
    public String endpoint() {
        return "no security required!";
    }

}