diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirement.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirement.java
index 989a7386fc..3a1b9d144c 100644
--- a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirement.java
+++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirement.java
@@ -11,9 +11,11 @@
import static java.lang.annotation.ElementType.METHOD;
/**
- * The annotation may be applied at class or method level, or in {@link io.swagger.v3.oas.annotations.Operation#security()} ()} to define security requirements for the
+ * The annotation may be applied at class or method level, or in {@link io.swagger.v3.oas.annotations.Operation#security()} to define security requirements for the
* single operation (when applied at method level) or for all operations of a class (when applied at class level).
*
It can also be used in {@link io.swagger.v3.oas.annotations.OpenAPIDefinition#security()} to define spec level security.
+ * {@link SecurityRequirement#combine()} can be used to define multiple security requirements at the same time, requiring each one of them.
+ * If only one of multiple security schemes is required, use multiple {@link SecurityRequirement} annotations.
*
* @see Security Requirement (OpenAPI specification)
* @see io.swagger.v3.oas.annotations.OpenAPIDefinition
@@ -26,10 +28,11 @@
public @interface SecurityRequirement {
/**
* This name must correspond to a declared SecurityRequirement.
+ * Exactly one of this and {@link #combine()} must be set.
*
* @return String name
*/
- String name();
+ String name() default "";
/**
* If the security scheme is of type "oauth2" or "openIdConnect", then the value is a list of scope names required for the execution.
@@ -38,4 +41,13 @@
* @return String array of scopes
*/
String[] scopes() default {};
+
+ /**
+ * If multiple requirements apply at the same time, use this value instead of {@link #name()} and {@link #scopes()}.
+ * If any one of multiple security schemes is required, use multiple {@link SecurityRequirement} annotations instead.
+ * Exactly one of this and {@link #name()} must be set.
+ *
+ * @return SecurityRequirementEntry array of requirements
+ */
+ SecurityRequirementEntry[] combine() default {};
}
diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirementEntry.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirementEntry.java
new file mode 100644
index 0000000000..4070c06c96
--- /dev/null
+++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/security/SecurityRequirementEntry.java
@@ -0,0 +1,33 @@
+package io.swagger.v3.oas.annotations.security;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation may be applied in {@link SecurityRequirement#combine()} to define combined security requirements for the
+ * single operation.
+ *
+ * @see Security Requirement (OpenAPI specification)
+ * @see io.swagger.v3.oas.annotations.security.SecurityRequirement
+ **/
+@Target({ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SecurityRequirementEntry {
+ /**
+ * This name must correspond to a declared SecurityRequirement.
+ *
+ * @return String name
+ */
+ String name();
+
+ /**
+ * If the security scheme is of type "oauth2" or "openIdConnect", then the value is a list of scope names required for the execution.
+ * For other security scheme types, the array must be empty.
+ *
+ * @return String array of scopes
+ */
+ String[] scopes() default {};
+}
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/SecurityParser.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/SecurityParser.java
index c0a6010b31..0d3eba846e 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/SecurityParser.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/SecurityParser.java
@@ -28,14 +28,28 @@ public static Optional> getSecurityRequirements(io.swa
}
List securityRequirements = new ArrayList<>();
for (io.swagger.v3.oas.annotations.security.SecurityRequirement securityRequirementApi : securityRequirementsApi) {
- if (StringUtils.isBlank(securityRequirementApi.name())) {
- continue;
- }
SecurityRequirement securityRequirement = new SecurityRequirement();
- if (securityRequirementApi.scopes().length > 0) {
- securityRequirement.addList(securityRequirementApi.name(), Arrays.asList(securityRequirementApi.scopes()));
- } else {
- securityRequirement.addList(securityRequirementApi.name());
+ if (securityRequirementApi.combine().length > 0) {
+ for (io.swagger.v3.oas.annotations.security.SecurityRequirementEntry entry : securityRequirementApi.combine()) {
+ if (StringUtils.isBlank(entry.name())) {
+ continue;
+ }
+ if (entry.scopes().length > 0) {
+ securityRequirement.addList(entry.name(), Arrays.asList(entry.scopes()));
+ } else {
+ securityRequirement.addList(entry.name());
+ }
+ }
+ }
+ else {
+ if (StringUtils.isBlank(securityRequirementApi.name())) {
+ continue;
+ }
+ if (securityRequirementApi.scopes().length > 0) {
+ securityRequirement.addList(securityRequirementApi.name(), Arrays.asList(securityRequirementApi.scopes()));
+ } else {
+ securityRequirement.addList(securityRequirementApi.name());
+ }
}
securityRequirements.add(securityRequirement);
}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/security/SecurityTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/security/SecurityTest.java
index bdaba471fa..a1cecd845b 100644
--- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/security/SecurityTest.java
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/annotations/security/SecurityTest.java
@@ -10,6 +10,7 @@
import io.swagger.v3.oas.annotations.security.OAuthFlows;
import io.swagger.v3.oas.annotations.security.OAuthScope;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirementEntry;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.testng.annotations.Test;
@@ -19,7 +20,7 @@
public class SecurityTest extends AbstractAnnotationTest {
@Test
- public void testSecuritySheme() {
+ public void testSecurityScheme() {
String openApiYAML = readIntoYaml(SecurityTest.OAuth2SchemeOnClass.class);
int start = openApiYAML.indexOf("components:");
String extractedYAML = openApiYAML.substring(start, openApiYAML.length() - 1);
@@ -90,7 +91,7 @@ public void testSecurityRequirement() throws IOException {
}
@Test
- public void testMultipleSecurityShemes() {
+ public void testMultipleSecuritySchemes() {
String openApiYAML = readIntoYaml(SecurityTest.MultipleSchemesOnClass.class);
int start = openApiYAML.indexOf("components:");
String extractedYAML = openApiYAML.substring(start, openApiYAML.length() - 1);
@@ -112,6 +113,30 @@ public void testMultipleSecurityShemes() {
}
+ @Test
+ public void testCombinedSecurityRequirements() {
+ String openApiYAML = readIntoYaml(SecurityTest.CombinedSecurityRequirementsOnClass.class);
+ int start = openApiYAML.indexOf("security:");
+ int end = openApiYAML.indexOf("components:");
+ String extractedYAML = openApiYAML.substring(start, end);
+ String expectedYAML = "security:\n" +
+ "- api_key: []\n" +
+ " myOauth2Security: []\n";
+ assertEquals(extractedYAML, expectedYAML);
+ }
+
+ @Test
+ public void testSecurityRequirementAlternatives() {
+ String openApiYAML = readIntoYaml(SecurityRequirementAlternativesOnClass.class);
+ int start = openApiYAML.indexOf("security:");
+ int end = openApiYAML.indexOf("components:");
+ String extractedYAML = openApiYAML.substring(start, end);
+ String expectedYAML = "security:\n" +
+ "- api_key: []\n" +
+ "- myOauth2Security: []\n";
+ assertEquals(extractedYAML, expectedYAML);
+ }
+
@Test
public void testTicket2767() {
String openApiYAML = readIntoYaml(SecurityTest.Ticket2767.class);
@@ -169,6 +194,34 @@ static class MultipleSchemesOnClass {
}
+ @OpenAPIDefinition(
+ security = {@SecurityRequirement(combine = { @SecurityRequirementEntry(name = "api_key"), @SecurityRequirementEntry(name = "myOauth2Security") })}
+ )
+ @SecurityScheme(name = "api_key", type = SecuritySchemeType.APIKEY, paramName = "API_KEY")
+ @SecurityScheme(name = "myOauth2Security",
+ type = SecuritySchemeType.OAUTH2,
+ in = SecuritySchemeIn.HEADER,
+ flows = @OAuthFlows(
+ implicit = @OAuthFlow(authorizationUrl = "http://url.com/auth",
+ scopes = @OAuthScope(name = "write:pets", description = "modify pets in your account"))))
+ static class CombinedSecurityRequirementsOnClass {
+
+ }
+
+ @OpenAPIDefinition(
+ security = {@SecurityRequirement(name = "api_key"), @SecurityRequirement(name = "myOauth2Security")}
+ )
+ @SecurityScheme(name = "api_key", type = SecuritySchemeType.APIKEY, paramName = "API_KEY")
+ @SecurityScheme(name = "myOauth2Security",
+ type = SecuritySchemeType.OAUTH2,
+ in = SecuritySchemeIn.HEADER,
+ flows = @OAuthFlows(
+ implicit = @OAuthFlow(authorizationUrl = "http://url.com/auth",
+ scopes = @OAuthScope(name = "write:pets", description = "modify pets in your account"))))
+ static class SecurityRequirementAlternativesOnClass {
+
+ }
+
@OpenAPIDefinition(
security = {@SecurityRequirement(name = "basicAuth")},