diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs
index e6f0894c..a8ae754a 100644
--- a/utoipa-gen/src/lib.rs
+++ b/utoipa-gen/src/lib.rs
@@ -1009,11 +1009,23 @@ pub fn derive_to_schema(input: TokenStream) -> TokenStream {
 /// (),
 /// ("name" = []),
 /// ("name" = ["scope1", "scope2"]),
+/// ("name" = ["scope1", "scope2"], "name2" = []),
 /// ```
 ///
 /// Leaving empty _`()`_ creates an empty [`SecurityRequirement`][security] this is useful when
 /// security requirement is optional for operation.
 ///
+/// You can define multiple security requirements within same parenthesis seperated by comma. This
+/// allows you to define keys that must be simultaneously provided for the endpoint / API.
+///
+/// _**Following could be explained as: Security is optional and if provided it must either contain
+/// `api_key` or `key AND key2`.**_
+/// ```text
+/// (),
+/// ("api_key" = []),
+/// ("key" = [], "key2" = []),
+/// ```
+///
 /// # actix_extras feature support for actix-web
 ///
 /// **actix_extras** feature gives **utoipa** ability to parse path operation information from **actix-web** types and macros.
diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs
index e24c3f94..ceb5928d 100644
--- a/utoipa-gen/src/openapi.rs
+++ b/utoipa-gen/src/openapi.rs
@@ -12,7 +12,7 @@ use proc_macro2::TokenStream;
 use quote::{format_ident, quote, quote_spanned, ToTokens};
 
 use crate::{
-    parse_utils, path::PATH_STRUCT_PREFIX, security_requirement::SecurityRequirementAttr, Array,
+    parse_utils, path::PATH_STRUCT_PREFIX, security_requirement::SecurityRequirementsAttr, Array,
     ExternalDocs, ResultExt,
 };
 
@@ -27,7 +27,7 @@ pub struct OpenApiAttr<'o> {
     paths: Punctuated<ExprPath, Comma>,
     components: Components,
     modifiers: Punctuated<Modifier, Comma>,
-    security: Option<Array<'static, SecurityRequirementAttr>>,
+    security: Option<Array<'static, SecurityRequirementsAttr>>,
     tags: Option<Array<'static, Tag>>,
     external_docs: Option<ExternalDocs>,
     servers: Punctuated<Server, Comma>,
diff --git a/utoipa-gen/src/path.rs b/utoipa-gen/src/path.rs
index abc9894f..e68a4c48 100644
--- a/utoipa-gen/src/path.rs
+++ b/utoipa-gen/src/path.rs
@@ -14,7 +14,7 @@ use syn::{Expr, ExprLit, Lit, LitStr, Type};
 use crate::component::{GenericType, TypeTree};
 use crate::path::request_body::RequestBody;
 use crate::{parse_utils, Deprecated};
-use crate::{schema_type::SchemaType, security_requirement::SecurityRequirementAttr, Array};
+use crate::{schema_type::SchemaType, security_requirement::SecurityRequirementsAttr, Array};
 
 use self::response::Response;
 use self::{parameter::Parameter, request_body::RequestBodyAttr, response::Responses};
@@ -37,7 +37,7 @@ pub struct PathAttr<'p> {
     operation_id: Option<Expr>,
     tag: Option<parse_utils::Value>,
     params: Vec<Parameter<'p>>,
-    security: Option<Array<'p, SecurityRequirementAttr>>,
+    security: Option<Array<'p, SecurityRequirementsAttr>>,
     context_path: Option<parse_utils::Value>,
 }
 
@@ -421,7 +421,7 @@ struct Operation<'a> {
     parameters: &'a Vec<Parameter<'a>>,
     request_body: Option<&'a RequestBody<'a>>,
     responses: &'a Vec<Response<'a>>,
-    security: Option<&'a Array<'a, SecurityRequirementAttr>>,
+    security: Option<&'a Array<'a, SecurityRequirementsAttr>>,
 }
 
 impl ToTokens for Operation<'_> {
diff --git a/utoipa-gen/src/security_requirement.rs b/utoipa-gen/src/security_requirement.rs
index a5934725..62bfa51d 100644
--- a/utoipa-gen/src/security_requirement.rs
+++ b/utoipa-gen/src/security_requirement.rs
@@ -12,23 +12,31 @@ use crate::Array;
 
 #[derive(Default)]
 #[cfg_attr(feature = "debug", derive(Debug))]
-pub struct SecurityRequirementAttr {
-    name: Option<String>,
-    scopes: Option<Vec<String>>,
+pub struct SecurityRequirementsAttrItem {
+    pub name: Option<String>,
+    pub scopes: Option<Vec<String>>,
 }
 
-impl Parse for SecurityRequirementAttr {
+#[derive(Default)]
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct SecurityRequirementsAttr(Punctuated<SecurityRequirementsAttrItem, Comma>);
+
+impl Parse for SecurityRequirementsAttr {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Punctuated::<SecurityRequirementsAttrItem, Comma>::parse_terminated(input)
+            .map(|o| Self(o.into_iter().collect()))
+    }
+}
+
+impl Parse for SecurityRequirementsAttrItem {
     fn parse(input: ParseStream) -> syn::Result<Self> {
-        if input.is_empty() {
-            return Ok(Self {
-                ..Default::default()
-            });
-        }
         let name = input.parse::<LitStr>()?.value();
+
         input.parse::<Token![=]>()?;
 
         let scopes_stream;
         bracketed!(scopes_stream in input);
+
         let scopes = Punctuated::<LitStr, Comma>::parse_terminated(&scopes_stream)?
             .iter()
             .map(LitStr::value)
@@ -41,19 +49,21 @@ impl Parse for SecurityRequirementAttr {
     }
 }
 
-impl ToTokens for SecurityRequirementAttr {
+impl ToTokens for SecurityRequirementsAttr {
     fn to_tokens(&self, tokens: &mut TokenStream) {
-        if let (Some(name), Some(scopes)) = (&self.name, &self.scopes) {
-            let scopes_array = scopes.iter().collect::<Array<&String>>();
-            let scopes_len = scopes.len();
-
-            tokens.extend(quote! {
-                utoipa::openapi::security::SecurityRequirement::new::<&str, [&str; #scopes_len], &str>(#name, #scopes_array)
-            })
-        } else {
-            tokens.extend(quote! {
-                utoipa::openapi::security::SecurityRequirement::default()
-            })
+        tokens.extend(quote! {
+            utoipa::openapi::security::SecurityRequirement::default()
+        });
+
+        for requirement in &self.0 {
+            if let (Some(name), Some(scopes)) = (&requirement.name, &requirement.scopes) {
+                let scopes = scopes.iter().collect::<Array<&String>>();
+                let scopes_len = scopes.len();
+
+                tokens.extend(quote! {
+                    .add::<&str, [&str; #scopes_len], &str>(#name, #scopes)
+                });
+            }
         }
     }
 }
diff --git a/utoipa-gen/tests/openapi_derive.rs b/utoipa-gen/tests/openapi_derive.rs
index a13c035f..5c6b3004 100644
--- a/utoipa-gen/tests/openapi_derive.rs
+++ b/utoipa-gen/tests/openapi_derive.rs
@@ -17,7 +17,8 @@ fn derive_openapi_with_security_requirement() {
     #[openapi(security(
             (),
             ("my_auth" = ["read:items", "edit:items"]),
-            ("token_jwt" = [])
+            ("token_jwt" = []),
+            ("api_key1" = [], "api_key2" = []),
         ))]
     struct ApiDoc;
 
@@ -28,6 +29,8 @@ fn derive_openapi_with_security_requirement() {
         "security.[1].my_auth.[0]" = r###""read:items""###, "api_oauth first scope"
         "security.[1].my_auth.[1]" = r###""edit:items""###, "api_oauth second scope"
         "security.[2].token_jwt" = "[]", "jwt_token auth scopes"
+        "security.[3].api_key1" = "[]", "api_key1 auth scopes"
+        "security.[3].api_key2" = "[]", "api_key2 auth scopes"
     }
 }
 
diff --git a/utoipa-gen/tests/openapi_derive_test.rs b/utoipa-gen/tests/openapi_derive_test.rs
index 162e0857..7d82eb69 100644
--- a/utoipa-gen/tests/openapi_derive_test.rs
+++ b/utoipa-gen/tests/openapi_derive_test.rs
@@ -37,7 +37,7 @@ mod pet_api {
         ),
         security(
             (),
-            ("my_auth" = ["read:items", "edit:items"]),
+            ("my_auth1" = ["read:items", "edit:items"], "my_auth2" = ["read:items"]),
             ("token_jwt" = [])
         )
     )]
@@ -58,7 +58,7 @@ mod pet_api {
     modifiers(&Foo),
     security(
         (),
-        ("my_auth" = ["read:items", "edit:items"]),
+        ("my_auth1" = ["read:items", "edit:items"], "my_auth2" = ["read:items"]),
         ("token_jwt" = [])
     )
 )]
diff --git a/utoipa/src/openapi/path.rs b/utoipa/src/openapi/path.rs
index c81acc4f..2a302f22 100644
--- a/utoipa/src/openapi/path.rs
+++ b/utoipa/src/openapi/path.rs
@@ -10,7 +10,8 @@ use super::{
     builder,
     request_body::RequestBody,
     response::{Response, Responses},
-    set_value, Deprecated, ExternalDocs, RefOr, Required, Schema, SecurityRequirement, Server,
+    security::SecurityRequirement,
+    set_value, Deprecated, ExternalDocs, RefOr, Required, Schema, Server,
 };
 
 #[cfg(not(feature = "preserve_path_order"))]
@@ -773,8 +774,8 @@ mod tests {
             SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
         let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
         let operation = OperationBuilder::new()
-            .security(security_requirement1)
-            .security(security_requirement2)
+            .security(security_requirement1.into())
+            .security(security_requirement2.into())
             .build();
 
         assert!(operation.security.is_some());
diff --git a/utoipa/src/openapi/security.rs b/utoipa/src/openapi/security.rs
index f5011d99..ca920215 100644
--- a/utoipa/src/openapi/security.rs
+++ b/utoipa/src/openapi/security.rs
@@ -53,6 +53,9 @@ impl SecurityRequirement {
     /// # use utoipa::openapi::security::SecurityRequirement;
     /// SecurityRequirement::default();
     /// ```
+    ///
+    /// If you have more than one name in the security requirement you can use
+    /// [`SecurityRequirement::add`].
     pub fn new<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
         name: N,
         scopes: S,
@@ -69,6 +72,69 @@ impl SecurityRequirement {
             })),
         }
     }
+
+    /// Allows to add multiple names to security requirement.
+    ///
+    /// Accepts name for the security requirement which must match to the name of available [`SecurityScheme`].
+    /// Second parameter is [`IntoIterator`] of [`Into<String>`] scopes needed by the [`SecurityRequirement`].
+    /// Scopes must match to the ones defined in [`SecurityScheme`].
+    ///
+    /// # Examples
+    ///
+    /// Make both API keys required:
+    /// ```rust
+    /// # use utoipa::openapi::security::{SecurityRequirement, HttpAuthScheme, HttpBuilder, SecurityScheme};
+    /// # use utoipa::{openapi, Modify, OpenApi};
+    /// # use serde::Serialize;
+    /// #[derive(Debug, Serialize)]
+    /// struct Foo;
+    ///
+    /// impl Modify for Foo {
+    ///     fn modify(&self, openapi: &mut openapi::OpenApi) {
+    ///         if let Some(schema) = openapi.components.as_mut() {
+    ///             schema.add_security_scheme(
+    ///                 "api_key1",
+    ///                 SecurityScheme::Http(
+    ///                     HttpBuilder::new()
+    ///                         .scheme(HttpAuthScheme::Bearer)
+    ///                         .bearer_format("JWT")
+    ///                         .build(),
+    ///                 ),
+    ///             );
+    ///             schema.add_security_scheme(
+    ///                 "api_key2",
+    ///                 SecurityScheme::Http(
+    ///                     HttpBuilder::new()
+    ///                         .scheme(HttpAuthScheme::Bearer)
+    ///                         .bearer_format("JWT")
+    ///                         .build(),
+    ///                 ),
+    ///             );
+    ///         }
+    ///     }
+    /// }
+    ///
+    /// #[derive(Default, OpenApi)]
+    /// #[openapi(
+    ///     modifiers(&Foo),
+    ///     security(
+    ///         ("api_key1" = ["edit:items", "read:items"], "api_key2" = ["edit:items", "read:items"]),
+    ///     )
+    /// )]
+    /// struct ApiDoc;
+    /// ```
+    pub fn add<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
+        mut self,
+        name: N,
+        scopes: S,
+    ) -> Self {
+        self.value.insert(
+            Into::<String>::into(name),
+            scopes.into_iter().map(Into::<String>::into).collect(),
+        );
+
+        self
+    }
 }
 
 /// OpenAPI [security scheme][security] for path operations.