This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 30923b03ebae CAMEL-26879: camel-keycloak, camel-shiro - add the 
security policy beans to the bean catalog (#29710)
30923b03ebae is described below

commit 30923b03ebaed556192475a1cd79a9b6b86cb62a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 12 17:07:49 2026 +0100

    CAMEL-26879: camel-keycloak, camel-shiro - add the security policy beans to 
the bean catalog (#29710)
    
    KeycloakSecurityPolicy and ShiroSecurityPolicy get pojo-bean metadata in 
the catalog, as CAMEL-26559 did for the opa, openfga and spiffe beans, so that 
camel run resolves their jars for a YAML route that declares them under beans: 
without --dep. Credentials are marked secret, and the bean and shiro.adoc 
descriptions of alwaysReauthenticate, rolesList, publicKey and the time options 
are corrected.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../org/apache/camel/catalog/beans.properties      |   2 +
 .../catalog/beans/KeycloakSecurityPolicy.json      |  16 ++
 .../camel/catalog/beans/ShiroSecurityPolicy.json   |  16 ++
 .../org/apache/camel/catalog/docs/shiro.adoc       |  10 +-
 .../org/apache/camel/catalog/CamelCatalogTest.java |   6 +-
 .../security/KeycloakSecurityPolicyConfigurer.java | 187 +++++++++++++++++++++
 .../services/org/apache/camel/bean.properties      |   7 +
 .../apache/camel/bean/KeycloakSecurityPolicy.json  |  16 ++
 ...ponent.keycloak.security.KeycloakSecurityPolicy |   2 +
 .../keycloak/security/KeycloakSecurityPolicy.java  |  70 ++++++++
 .../KeycloakSecurityPolicyBindingTest.java         |  91 ++++++++++
 .../security/ShiroSecurityPolicyConfigurer.java    | 108 ++++++++++++
 .../services/org/apache/camel/bean.properties      |   7 +
 .../org/apache/camel/bean/ShiroSecurityPolicy.json |  16 ++
 ...el.component.shiro.security.ShiroSecurityPolicy |   2 +
 components/camel-shiro/src/main/docs/shiro.adoc    |  10 +-
 .../shiro/security/ShiroSecurityPolicy.java        |  30 ++++
 .../security/ShiroSecurityPolicyBindingTest.java   |  77 +++++++++
 .../java/org/apache/camel/util/ArtifactUtils.java  |   4 +
 19 files changed, 668 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
index dc3b21b7dcaf..10f0d5a67519 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
@@ -41,6 +41,7 @@ KafkaIdempotentRepository
 KafkaKeyValueRepository
 KeyValueAggregationRepository
 KeyValueIdempotentRepository
+KeycloakSecurityPolicy
 KubernetesClusterService
 LoggingHttpActivityListener
 MemoryAggregationRepository
@@ -53,6 +54,7 @@ OpenFgaSecurityPolicy
 OpensearchBulkRequestAggregationStrategy
 RedisAggregationRepository
 RedisKeyValueRepository
+ShiroSecurityPolicy
 SimpleScheduledRoutePolicy
 SpiffeSSLContextParameters
 SpiffeSecurityPolicy
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/KeycloakSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/KeycloakSecurityPolicy.json
new file mode 100644
index 000000000000..22a7f1d27fec
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/KeycloakSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "KeycloakSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Keycloak Security Policy",
+    "description": "An authorization policy that validates the caller's 
Keycloak access token and checks its roles and permissions before the part of 
the route it guards runs. A caller that fails is denied with 
CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-keycloak",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "serverUrl": { "index": 0, "kind": "property", 
"displayName": "Server Url", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The base URL of the Keycloak server, for example 
http:\/\/localhost:8080." }, "realm": { "index": 1, "kind": "property", 
"displayName": "Realm", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/ShiroSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/ShiroSecurityPolicy.json
new file mode 100644
index 000000000000..9b3ef1a5f00d
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/ShiroSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "ShiroSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.shiro.security.ShiroSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Shiro Security Policy",
+    "description": "An authorization policy that authenticates the caller with 
Apache Shiro, from the encrypted ShiroSecurityToken sent with the message, and 
checks its permissions and roles. Create it with the Shiro INI resource as its 
constructor argument, or set securityManager.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-shiro",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "cipherService": { "index": 0, "kind": "property", 
"displayName": "Cipher Service", "label": "advanced", "required": false, 
"type": "object", "javaType": "org.apache.shiro.crypto.cipher.CipherService", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
cipher service that decrypts the ShiroSecurityToken sent with the message. AES 
by default; Shiro also ships a Blowfish one." }, "passPhrase": { "index": 1, 
"kind": "property", "displayName": " [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/shiro.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/shiro.adoc
index e57ce7d6e0e1..f617b5afd510 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/shiro.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/shiro.adoc
@@ -120,9 +120,10 @@ acquired from the file system, classpath, or URLs when 
prefixed with
 |`passPhrase` |`An AES 128 based key` |byte[] |A passPhrase to decrypt 
ShiroSecurityToken(s) sent along with Message
 Exchanges
 
-|`alwaysReauthenticate` |`true` |boolean |Setting to ensure re-authentication 
on every request. If set
-to false, the user is authenticated and locked such than only requests
-from the same user going forward are authenticated.
+|`alwaysReauthenticate` |`true` |boolean |Whether to log the caller out after 
every exchange. If set
+to false, the authenticated subject stays bound to the worker thread for
+the next exchange on that thread. The token of every exchange is
+authenticated either way.
 
 |`permissionsList` |`none` |List<Permission> |A List of permissions required 
in order for an authenticated user to be
 authorized to perform further action i.e., continue further on the route.
@@ -136,7 +137,8 @@ user to be authorized to perform further action i.e., 
continue further on
 the route. If no roles list or permissions list (see above) is provided
 to the ShiroSecurityPolicy object, then authorization is deemed as not
 required. Note that the default is that authorization is granted if any
-of the roles in the list are applicable.
+of the roles in the list are applicable. The roles are checked only when
+no permissions list is set: when both are set, the permissions decide.
 
 |`cipherService` |`AES` |org.apache.shiro.crypto.CipherService |Shiro ships 
with AES & Blowfish-based CipherServices. You may use one of
 these or pass in your own Cipher implementation
diff --git 
a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
index 7a8adb8a2ef2..e6fdb4a424ec 100644
--- 
a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
+++ 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
@@ -2276,7 +2276,11 @@ public class CamelCatalogTest {
                 Arguments.of("SpiffeSecurityPolicy", 
"org.apache.camel.component.spiffe.SpiffeSecurityPolicy",
                         "camel-spiffe", 
"org.apache.camel.spi.AuthorizationPolicy", "acceptedSpiffeIds"),
                 Arguments.of("SpiffeSSLContextParameters", 
"org.apache.camel.component.spiffe.SpiffeSSLContextParameters",
-                        "camel-spiffe", 
"org.apache.camel.support.jsse.SSLContextParameters", "acceptedSpiffeIds"));
+                        "camel-spiffe", 
"org.apache.camel.support.jsse.SSLContextParameters", "acceptedSpiffeIds"),
+                Arguments.of("KeycloakSecurityPolicy", 
"org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy",
+                        "camel-keycloak", 
"org.apache.camel.spi.AuthorizationPolicy", "realm"),
+                Arguments.of("ShiroSecurityPolicy", 
"org.apache.camel.component.shiro.security.ShiroSecurityPolicy",
+                        "camel-shiro", 
"org.apache.camel.spi.AuthorizationPolicy", "passPhrase"));
     }
 
     // camel run looks up a bean class it cannot load by its simple name here, 
to find the jar to download
diff --git 
a/components/camel-keycloak/src/generated/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyConfigurer.java
 
b/components/camel-keycloak/src/generated/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..7ffff53502b8
--- /dev/null
+++ 
b/components/camel-keycloak/src/generated/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyConfigurer.java
@@ -0,0 +1,187 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.keycloak.security;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class KeycloakSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy 
target = (org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy) 
obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": 
target.setAllPermissionsRequired(property(camelContext, boolean.class, value)); 
return true;
+        case "allrolesrequired":
+        case "allRolesRequired": 
target.setAllRolesRequired(property(camelContext, boolean.class, value)); 
return true;
+        case "allowtokenfromheader":
+        case "allowTokenFromHeader": 
target.setAllowTokenFromHeader(property(camelContext, boolean.class, value)); 
return true;
+        case "autofetchpublickey":
+        case "autoFetchPublicKey": 
target.setAutoFetchPublicKey(property(camelContext, boolean.class, value)); 
return true;
+        case "clientid":
+        case "clientId": target.setClientId(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "clientsecret":
+        case "clientSecret": target.setClientSecret(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "expectedaudience":
+        case "expectedAudience": 
target.setExpectedAudience(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "expectedauthorizedparty":
+        case "expectedAuthorizedParty": 
target.setExpectedAuthorizedParty(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "expectedtokentypes":
+        case "expectedTokenTypes": 
target.setExpectedTokenTypes(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "introspectioncacheenabled":
+        case "introspectionCacheEnabled": 
target.setIntrospectionCacheEnabled(property(camelContext, boolean.class, 
value)); return true;
+        case "introspectioncachettl":
+        case "introspectionCacheTtl": 
target.setIntrospectionCacheTtl(property(camelContext, long.class, value)); 
return true;
+        case "jwksrefreshinterval":
+        case "jwksRefreshInterval": 
target.setJwksRefreshInterval(property(camelContext, long.class, value)); 
return true;
+        case "minjwksrefreshinterval":
+        case "minJwksRefreshInterval": 
target.setMinJwksRefreshInterval(property(camelContext, long.class, value)); 
return true;
+        case "password": target.setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "preferpropertyoverheader":
+        case "preferPropertyOverHeader": 
target.setPreferPropertyOverHeader(property(camelContext, boolean.class, 
value)); return true;
+        case "publickey":
+        case "publicKey": target.setPublicKey(property(camelContext, 
java.security.PublicKey.class, value)); return true;
+        case "realm": target.setRealm(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "requiredpermissions":
+        case "requiredPermissions": 
target.setRequiredPermissions(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "requiredroles":
+        case "requiredRoles": target.setRequiredRoles(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "serverurl":
+        case "serverUrl": target.setServerUrl(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "useresourceownerpasswordcredentials":
+        case "useResourceOwnerPasswordCredentials": 
target.setUseResourceOwnerPasswordCredentials(property(camelContext, 
boolean.class, value)); return true;
+        case "usetokenintrospection":
+        case "useTokenIntrospection": 
target.setUseTokenIntrospection(property(camelContext, boolean.class, value)); 
return true;
+        case "username": target.setUsername(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "validateissuer":
+        case "validateIssuer": target.setValidateIssuer(property(camelContext, 
boolean.class, value)); return true;
+        case "validatetokenbinding":
+        case "validateTokenBinding": 
target.setValidateTokenBinding(property(camelContext, boolean.class, value)); 
return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": return boolean.class;
+        case "allrolesrequired":
+        case "allRolesRequired": return boolean.class;
+        case "allowtokenfromheader":
+        case "allowTokenFromHeader": return boolean.class;
+        case "autofetchpublickey":
+        case "autoFetchPublicKey": return boolean.class;
+        case "clientid":
+        case "clientId": return java.lang.String.class;
+        case "clientsecret":
+        case "clientSecret": return java.lang.String.class;
+        case "expectedaudience":
+        case "expectedAudience": return java.lang.String.class;
+        case "expectedauthorizedparty":
+        case "expectedAuthorizedParty": return java.lang.String.class;
+        case "expectedtokentypes":
+        case "expectedTokenTypes": return java.lang.String.class;
+        case "introspectioncacheenabled":
+        case "introspectionCacheEnabled": return boolean.class;
+        case "introspectioncachettl":
+        case "introspectionCacheTtl": return long.class;
+        case "jwksrefreshinterval":
+        case "jwksRefreshInterval": return long.class;
+        case "minjwksrefreshinterval":
+        case "minJwksRefreshInterval": return long.class;
+        case "password": return java.lang.String.class;
+        case "preferpropertyoverheader":
+        case "preferPropertyOverHeader": return boolean.class;
+        case "publickey":
+        case "publicKey": return java.security.PublicKey.class;
+        case "realm": return java.lang.String.class;
+        case "requiredpermissions":
+        case "requiredPermissions": return java.lang.String.class;
+        case "requiredroles":
+        case "requiredRoles": return java.lang.String.class;
+        case "serverurl":
+        case "serverUrl": return java.lang.String.class;
+        case "useresourceownerpasswordcredentials":
+        case "useResourceOwnerPasswordCredentials": return boolean.class;
+        case "usetokenintrospection":
+        case "useTokenIntrospection": return boolean.class;
+        case "username": return java.lang.String.class;
+        case "validateissuer":
+        case "validateIssuer": return boolean.class;
+        case "validatetokenbinding":
+        case "validateTokenBinding": return boolean.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy 
target = (org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy) 
obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": return 
target.isAllPermissionsRequired();
+        case "allrolesrequired":
+        case "allRolesRequired": return target.isAllRolesRequired();
+        case "allowtokenfromheader":
+        case "allowTokenFromHeader": return target.isAllowTokenFromHeader();
+        case "autofetchpublickey":
+        case "autoFetchPublicKey": return target.isAutoFetchPublicKey();
+        case "clientid":
+        case "clientId": return target.getClientId();
+        case "clientsecret":
+        case "clientSecret": return target.getClientSecret();
+        case "expectedaudience":
+        case "expectedAudience": return target.getExpectedAudience();
+        case "expectedauthorizedparty":
+        case "expectedAuthorizedParty": return 
target.getExpectedAuthorizedParty();
+        case "expectedtokentypes":
+        case "expectedTokenTypes": return target.getExpectedTokenTypes();
+        case "introspectioncacheenabled":
+        case "introspectionCacheEnabled": return 
target.isIntrospectionCacheEnabled();
+        case "introspectioncachettl":
+        case "introspectionCacheTtl": return target.getIntrospectionCacheTtl();
+        case "jwksrefreshinterval":
+        case "jwksRefreshInterval": return target.getJwksRefreshInterval();
+        case "minjwksrefreshinterval":
+        case "minJwksRefreshInterval": return 
target.getMinJwksRefreshInterval();
+        case "password": return target.getPassword();
+        case "preferpropertyoverheader":
+        case "preferPropertyOverHeader": return 
target.isPreferPropertyOverHeader();
+        case "publickey":
+        case "publicKey": return target.getPublicKey();
+        case "realm": return target.getRealm();
+        case "requiredpermissions":
+        case "requiredPermissions": return target.getRequiredPermissions();
+        case "requiredroles":
+        case "requiredRoles": return target.getRequiredRoles();
+        case "serverurl":
+        case "serverUrl": return target.getServerUrl();
+        case "useresourceownerpasswordcredentials":
+        case "useResourceOwnerPasswordCredentials": return 
target.isUseResourceOwnerPasswordCredentials();
+        case "usetokenintrospection":
+        case "useTokenIntrospection": return target.isUseTokenIntrospection();
+        case "username": return target.getUsername();
+        case "validateissuer":
+        case "validateIssuer": return target.isValidateIssuer();
+        case "validatetokenbinding":
+        case "validateTokenBinding": return target.isValidateTokenBinding();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
new file mode 100644
index 000000000000..da7d9ecb0ea4
--- /dev/null
+++ 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+bean=KeycloakSecurityPolicy
+groupId=org.apache.camel
+artifactId=camel-keycloak
+version=4.24.0-SNAPSHOT
+projectName=Camel :: Keycloak
+projectDescription=Security using Keycloak
diff --git 
a/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean/KeycloakSecurityPolicy.json
 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean/KeycloakSecurityPolicy.json
new file mode 100644
index 000000000000..22a7f1d27fec
--- /dev/null
+++ 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/bean/KeycloakSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "KeycloakSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Keycloak Security Policy",
+    "description": "An authorization policy that validates the caller's 
Keycloak access token and checks its roles and permissions before the part of 
the route it guards runs. A caller that fails is denied with 
CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-keycloak",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "serverUrl": { "index": 0, "kind": "property", 
"displayName": "Server Url", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The base URL of the Keycloak server, for example 
http:\/\/localhost:8080." }, "realm": { "index": 1, "kind": "property", 
"displayName": "Realm", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false [...]
+  }
+}
+
diff --git 
a/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
new file mode 100644
index 000000000000..185de325ffe3
--- /dev/null
+++ 
b/components/camel-keycloak/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.keycloak.security.KeycloakSecurityPolicyConfigurer
diff --git 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
index 3bd318de6a7c..f8d10c3bcd05 100644
--- 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
+++ 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
@@ -26,61 +26,110 @@ import org.apache.camel.NamedNode;
 import org.apache.camel.Processor;
 import org.apache.camel.Route;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
 import org.apache.camel.util.ObjectHelper;
 import org.keycloak.admin.client.Keycloak;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@Metadata(label = "bean",
+          description = "An authorization policy that validates the caller's 
Keycloak access token and checks its roles and"
+                        + " permissions before the part of the route it guards 
runs. A caller that fails is denied with"
+                        + " CamelAuthorizationException.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
 public class KeycloakSecurityPolicy implements AuthorizationPolicy {
     private static final Logger LOG = 
LoggerFactory.getLogger(KeycloakSecurityPolicy.class);
 
+    @Metadata(description = "The base URL of the Keycloak server, for example 
http://localhost:8080.";)
     private String serverUrl;
+    @Metadata(description = "The Keycloak realm the tokens are issued in.")
     private String realm;
+    @Metadata(description = "The client id of this application in the realm.")
     private String clientId;
+    @Metadata(label = "security", security = "secret",
+              description = "The client secret, for a confidential client.")
     private String clientSecret;
+    @Metadata(label = "security", security = "secret",
+              description = "The user to sign in as when 
useResourceOwnerPasswordCredentials is enabled.")
     private String username;
+    @Metadata(label = "security", security = "secret",
+              description = "The password of that user.")
     private String password;
     /**
      * Comma-separated list of required roles for authorization. Example: 
"admin,user,manager"
      */
+    @Metadata(description = "Comma-separated list of the roles the caller must 
have, for example admin,user. Whether it needs"
+                            + " all of them or any one is set by 
allRolesRequired.")
     private String requiredRoles;
     /**
      * Comma-separated list of required permissions for authorization. 
Example: "read:documents,write:documents"
      */
+    @Metadata(description = "Comma-separated list of the permissions the 
caller must have, for example"
+                            + " read:documents,write:documents. Whether it 
needs all of them or any one is set by"
+                            + " allPermissionsRequired.")
     private String requiredPermissions;
+    @Metadata(defaultValue = "true",
+              description = "Whether the caller must have all the required 
roles, rather than any one of them.")
     private boolean allRolesRequired = true;
+    @Metadata(defaultValue = "true",
+              description = "Whether the caller must have all the required 
permissions, rather than any one of them.")
     private boolean allPermissionsRequired = true;
+    @Metadata(label = "security", defaultValue = "false",
+              description = "Whether the policy's Keycloak client signs in 
with username and password, the resource owner"
+                            + " password credentials grant, rather than with 
clientId and clientSecret.")
     private boolean useResourceOwnerPasswordCredentials = false;
+    @Metadata(label = "security",
+              description = "The public key to verify token signatures with. 
Used only when autoFetchPublicKey is false,"
+                            + " which it is not by default.")
     private PublicKey publicKey;
     /**
      * Enable OAuth 2.0 token introspection for real-time token validation. 
When enabled, tokens are validated by
      * calling Keycloak's introspection endpoint instead of local JWT parsing. 
This allows detecting revoked tokens
      * before expiration.
      */
+    @Metadata(label = "security", defaultValue = "false",
+              description = "Whether to validate tokens with Keycloak's token 
introspection endpoint rather than by parsing"
+                            + " the JWT locally, which detects a token revoked 
before it expires.")
     private boolean useTokenIntrospection = false;
     /**
      * Enable caching of token introspection results to reduce API calls to 
Keycloak.
      */
+    @Metadata(label = "advanced", defaultValue = "true",
+              description = "Whether to cache token introspection results, to 
reduce the calls to Keycloak.")
     private boolean introspectionCacheEnabled = true;
     /**
      * Time-to-live for cached introspection results in seconds. Default is 60 
seconds.
      */
+    @Metadata(label = "advanced", defaultValue = "60",
+              description = "How long a cached introspection result is kept, 
in seconds, given as a plain number rather"
+                            + " than a time pattern such as 5m.")
     private long introspectionCacheTtl = 60;
     /**
      * Enable validation that tokens are bound to specific sessions. When 
enabled, tokens from headers must match the
      * session-bound token to prevent session fixation attacks. Default is 
true for security.
      */
+    @Metadata(label = "security", defaultValue = "true",
+              description = "Whether a token from a header must match the 
token bound to the session, which prevents session"
+                            + " fixation.")
     private boolean validateTokenBinding = true;
     /**
      * Allow token retrieval from HTTP headers. When disabled, tokens can only 
come from exchange properties. Disabling
      * this prevents attackers from injecting tokens via headers. Default is 
true for backward compatibility, but should
      * be set to false in production environments where tokens are set 
programmatically.
      */
+    @Metadata(label = "security", defaultValue = "true",
+              description = "Whether the token may come from a message header. 
Disable it where the route sets the token"
+                            + " itself, so a caller cannot inject one through 
a header.")
     private boolean allowTokenFromHeader = true;
     /**
      * Prefer tokens from exchange properties over headers. When true, if a 
token exists in both property and header,
      * the property value is used. This prevents header-based token override 
attacks. Default is true for security.
      */
+    @Metadata(label = "security", defaultValue = "true",
+              description = "Whether a token in an exchange property wins over 
one in a header, which prevents a header"
+                            + " from overriding the token the route 
established.")
     private boolean preferPropertyOverHeader = true;
 
     private Keycloak keycloakClient;
@@ -91,21 +140,34 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
      * an issuer that does not match the configured serverUrl and realm will 
be rejected. This prevents cross-realm
      * token injection attacks in multi-tenant environments.
      */
+    @Metadata(label = "security", defaultValue = "true",
+              description = "Whether a token must have been issued by the 
configured serverUrl and realm, which prevents"
+                            + " cross-realm token injection.")
     private boolean validateIssuer = true;
     /**
      * Enable automatic fetching of public keys from the Keycloak JWKS 
endpoint for signature verification. When enabled
      * (default), public keys are automatically fetched and cached from
      * {serverUrl}/realms/{realm}/protocol/openid-connect/certs. This ensures 
token signatures are properly verified.
      */
+    @Metadata(label = "security", defaultValue = "true",
+              description = "Whether to fetch the keys that verify token 
signatures from the realm's JWKS endpoint, and"
+                            + " cache them.")
     private boolean autoFetchPublicKey = true;
     /**
      * Cache refresh interval for Keycloak JWKS public keys in seconds. 
Default is 300 seconds (5 minutes).
      */
+    @Metadata(label = "advanced", defaultValue = "300",
+              description = "How often the cached JWKS keys are refreshed, in 
seconds, given as a plain number rather than"
+                            + " a time pattern such as 5m.")
     private long jwksRefreshInterval = 300;
     /**
      * Minimum cooldown interval in seconds between remote JWKS refreshes when 
an unknown key ID (kid) is encountered.
      * This rate-limits outbound calls and prevents JWKS cache stampede. 
Default is 10 seconds.
      */
+    @Metadata(label = "advanced", defaultValue = "10",
+              description = "The minimum time between two JWKS refreshes 
triggered by an unknown key id, which"
+                            + " rate-limits the calls to Keycloak, in seconds, 
given as a plain number rather than a time"
+                            + " pattern such as 5m.")
     private long minJwksRefreshInterval = 10;
     /**
      * Comma-separated list of expected audiences for token validation. When 
set, tokens whose "aud" claim does not
@@ -113,6 +175,9 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
      * behavior). This prevents a token issued for one client from being 
accepted by routes intended for another client
      * in multi-client Keycloak realms. Disabled by default for backward 
compatibility. Example: "my-client"
      */
+    @Metadata(label = "security",
+              description = "Comma-separated list of audiences a token must 
all carry in its aud claim, so that a token"
+                            + " issued for another client of the realm is 
rejected.")
     private String expectedAudience;
 
     /**
@@ -121,6 +186,9 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
      * being presented where an access token is expected. Disabled by default 
for backward compatibility. Example:
      * "Bearer"
      */
+    @Metadata(label = "security",
+              description = "Comma-separated list of the accepted token types 
(typ claim), for example Bearer, so that an"
+                            + " ID or refresh token is not accepted where an 
access token is expected.")
     private String expectedTokenTypes;
 
     /**
@@ -128,6 +196,8 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
      * value is rejected, ensuring the token was issued for the expected 
client. Disabled by default for backward
      * compatibility. Example: "my-client"
      */
+    @Metadata(label = "security",
+              description = "The client a token must have been issued for (azp 
claim).")
     private String expectedAuthorizedParty;
 
     public KeycloakSecurityPolicy() {
diff --git 
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyBindingTest.java
 
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyBindingTest.java
new file mode 100644
index 000000000000..9f944ce61e10
--- /dev/null
+++ 
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyBindingTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.keycloak.security;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Binds the policy the way a YAML {@code beans:} entry does, where every 
value arrives as a string.
+ */
+class KeycloakSecurityPolicyBindingTest {
+
+    @Test
+    void bindsTheOptionsAsAYamlBeanDefinitionGivesThem() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("serverUrl", "https://keycloak:8443";);
+            properties.put("realm", "camel");
+            properties.put("clientId", "camel-client");
+            properties.put("clientSecret", "s3cr3t");
+            // a string, although setRequiredRoles and setRequiredPermissions 
also take a List
+            properties.put("requiredRoles", "admin,user");
+            properties.put("requiredPermissions", "read:documents");
+            properties.put("allRolesRequired", "false");
+            properties.put("useTokenIntrospection", "true");
+            properties.put("introspectionCacheTtl", "120");
+            properties.put("allowTokenFromHeader", "false");
+            properties.put("validateIssuer", "true");
+            properties.put("jwksRefreshInterval", "600");
+            properties.put("minJwksRefreshInterval", "30");
+            properties.put("expectedAudience", "camel-client");
+            properties.put("expectedTokenTypes", "Bearer");
+            properties.put("expectedAuthorizedParty", "camel-client");
+
+            KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            // the generated configurer binds the options, not reflection: it 
is found by the class name
+            PropertyConfigurer configurer = 
PluginHelper.getConfigurerResolver(context)
+                    
.resolvePropertyConfigurer(KeycloakSecurityPolicy.class.getName(), context);
+            assertNotNull(configurer);
+            assertEquals("KeycloakSecurityPolicyConfigurer", 
configurer.getClass().getSimpleName());
+
+            assertEquals("https://keycloak:8443";, policy.getServerUrl());
+            assertEquals("camel", policy.getRealm());
+            assertEquals("camel-client", policy.getClientId());
+            assertEquals("s3cr3t", policy.getClientSecret());
+            assertEquals(List.of("admin", "user"), 
policy.getRequiredRolesAsList());
+            assertEquals(List.of("read:documents"), 
policy.getRequiredPermissionsAsList());
+            assertFalse(policy.isAllRolesRequired());
+            assertTrue(policy.isAllPermissionsRequired());
+            assertTrue(policy.isUseTokenIntrospection());
+            assertEquals(120L, policy.getIntrospectionCacheTtl());
+            assertFalse(policy.isAllowTokenFromHeader());
+            assertTrue(policy.isValidateIssuer());
+            assertEquals(600L, policy.getJwksRefreshInterval());
+            assertEquals(30L, policy.getMinJwksRefreshInterval());
+            assertEquals("camel-client", policy.getExpectedAudience());
+            assertEquals("Bearer", policy.getExpectedTokenTypes());
+            assertEquals("camel-client", policy.getExpectedAuthorizedParty());
+        }
+    }
+}
diff --git 
a/components/camel-shiro/src/generated/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyConfigurer.java
 
b/components/camel-shiro/src/generated/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..2cd5ac7c353b
--- /dev/null
+++ 
b/components/camel-shiro/src/generated/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyConfigurer.java
@@ -0,0 +1,108 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.shiro.security;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.shiro.security.ShiroSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class ShiroSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.shiro.security.ShiroSecurityPolicy target = 
(org.apache.camel.component.shiro.security.ShiroSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": 
target.setAllPermissionsRequired(property(camelContext, boolean.class, value)); 
return true;
+        case "allrolesrequired":
+        case "allRolesRequired": 
target.setAllRolesRequired(property(camelContext, boolean.class, value)); 
return true;
+        case "alwaysreauthenticate":
+        case "alwaysReauthenticate": 
target.setAlwaysReauthenticate(property(camelContext, boolean.class, value)); 
return true;
+        case "base64": target.setBase64(property(camelContext, boolean.class, 
value)); return true;
+        case "cipherservice":
+        case "cipherService": target.setCipherService(property(camelContext, 
org.apache.shiro.crypto.cipher.CipherService.class, value)); return true;
+        case "passphrase":
+        case "passPhrase": target.setPassPhrase(property(camelContext, 
byte[].class, value)); return true;
+        case "permissionslist":
+        case "permissionsList": 
target.setPermissionsList(property(camelContext, java.util.List.class, value)); 
return true;
+        case "roleslist":
+        case "rolesList": target.setRolesList(property(camelContext, 
java.util.List.class, value)); return true;
+        case "securitymanager":
+        case "securityManager": 
target.setSecurityManager(property(camelContext, 
org.apache.shiro.mgt.SecurityManager.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": return boolean.class;
+        case "allrolesrequired":
+        case "allRolesRequired": return boolean.class;
+        case "alwaysreauthenticate":
+        case "alwaysReauthenticate": return boolean.class;
+        case "base64": return boolean.class;
+        case "cipherservice":
+        case "cipherService": return 
org.apache.shiro.crypto.cipher.CipherService.class;
+        case "passphrase":
+        case "passPhrase": return byte[].class;
+        case "permissionslist":
+        case "permissionsList": return java.util.List.class;
+        case "roleslist":
+        case "rolesList": return java.util.List.class;
+        case "securitymanager":
+        case "securityManager": return 
org.apache.shiro.mgt.SecurityManager.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.shiro.security.ShiroSecurityPolicy target = 
(org.apache.camel.component.shiro.security.ShiroSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allpermissionsrequired":
+        case "allPermissionsRequired": return 
target.isAllPermissionsRequired();
+        case "allrolesrequired":
+        case "allRolesRequired": return target.isAllRolesRequired();
+        case "alwaysreauthenticate":
+        case "alwaysReauthenticate": return target.isAlwaysReauthenticate();
+        case "base64": return target.isBase64();
+        case "cipherservice":
+        case "cipherService": return target.getCipherService();
+        case "passphrase":
+        case "passPhrase": return target.getPassPhrase();
+        case "permissionslist":
+        case "permissionsList": return target.getPermissionsList();
+        case "roleslist":
+        case "rolesList": return target.getRolesList();
+        case "securitymanager":
+        case "securityManager": return target.getSecurityManager();
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getCollectionValueType(Object target, String name, boolean 
ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "permissionslist":
+        case "permissionsList": return org.apache.shiro.authz.Permission.class;
+        case "roleslist":
+        case "rolesList": return java.lang.String.class;
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
new file mode 100644
index 000000000000..6005a5a40a63
--- /dev/null
+++ 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+bean=ShiroSecurityPolicy
+groupId=org.apache.camel
+artifactId=camel-shiro
+version=4.24.0-SNAPSHOT
+projectName=Camel :: Shiro
+projectDescription=Security using Shiro
diff --git 
a/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean/ShiroSecurityPolicy.json
 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean/ShiroSecurityPolicy.json
new file mode 100644
index 000000000000..9b3ef1a5f00d
--- /dev/null
+++ 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/bean/ShiroSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "ShiroSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.shiro.security.ShiroSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Shiro Security Policy",
+    "description": "An authorization policy that authenticates the caller with 
Apache Shiro, from the encrypted ShiroSecurityToken sent with the message, and 
checks its permissions and roles. Create it with the Shiro INI resource as its 
constructor argument, or set securityManager.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-shiro",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "cipherService": { "index": 0, "kind": "property", 
"displayName": "Cipher Service", "label": "advanced", "required": false, 
"type": "object", "javaType": "org.apache.shiro.crypto.cipher.CipherService", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
cipher service that decrypts the ShiroSecurityToken sent with the message. AES 
by default; Shiro also ships a Blowfish one." }, "passPhrase": { "index": 1, 
"kind": "property", "displayName": " [...]
+  }
+}
+
diff --git 
a/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.shiro.security.ShiroSecurityPolicy
 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.shiro.security.ShiroSecurityPolicy
new file mode 100644
index 000000000000..50ea33f9e232
--- /dev/null
+++ 
b/components/camel-shiro/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.shiro.security.ShiroSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.shiro.security.ShiroSecurityPolicyConfigurer
diff --git a/components/camel-shiro/src/main/docs/shiro.adoc 
b/components/camel-shiro/src/main/docs/shiro.adoc
index e57ce7d6e0e1..f617b5afd510 100644
--- a/components/camel-shiro/src/main/docs/shiro.adoc
+++ b/components/camel-shiro/src/main/docs/shiro.adoc
@@ -120,9 +120,10 @@ acquired from the file system, classpath, or URLs when 
prefixed with
 |`passPhrase` |`An AES 128 based key` |byte[] |A passPhrase to decrypt 
ShiroSecurityToken(s) sent along with Message
 Exchanges
 
-|`alwaysReauthenticate` |`true` |boolean |Setting to ensure re-authentication 
on every request. If set
-to false, the user is authenticated and locked such than only requests
-from the same user going forward are authenticated.
+|`alwaysReauthenticate` |`true` |boolean |Whether to log the caller out after 
every exchange. If set
+to false, the authenticated subject stays bound to the worker thread for
+the next exchange on that thread. The token of every exchange is
+authenticated either way.
 
 |`permissionsList` |`none` |List<Permission> |A List of permissions required 
in order for an authenticated user to be
 authorized to perform further action i.e., continue further on the route.
@@ -136,7 +137,8 @@ user to be authorized to perform further action i.e., 
continue further on
 the route. If no roles list or permissions list (see above) is provided
 to the ShiroSecurityPolicy object, then authorization is deemed as not
 required. Note that the default is that authorization is granted if any
-of the roles in the list are applicable.
+of the roles in the list are applicable. The roles are checked only when
+no permissions list is set: when both are set, the permissions decide.
 
 |`cipherService` |`AES` |org.apache.shiro.crypto.CipherService |Shiro ships 
with AES & Blowfish-based CipherServices. You may use one of
 these or pass in your own Cipher implementation
diff --git 
a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicy.java
 
b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicy.java
index 93e794bac6bf..b0892f3c4bc8 100644
--- 
a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicy.java
+++ 
b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicy.java
@@ -23,6 +23,8 @@ import org.apache.camel.NamedNode;
 import org.apache.camel.Processor;
 import org.apache.camel.Route;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.config.Ini;
 import org.apache.shiro.crypto.cipher.AesCipherService;
@@ -32,16 +34,44 @@ import org.apache.shiro.mgt.SecurityManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@Metadata(label = "bean",
+          description = "An authorization policy that authenticates the caller 
with Apache Shiro, from the encrypted"
+                        + " ShiroSecurityToken sent with the message, and 
checks its permissions and roles. Create it with"
+                        + " the Shiro INI resource as its constructor 
argument, or set securityManager.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
 public class ShiroSecurityPolicy implements AuthorizationPolicy {
     private static final Logger LOG = 
LoggerFactory.getLogger(ShiroSecurityPolicy.class);
+    @Metadata(label = "advanced",
+              description = "The cipher service that decrypts the 
ShiroSecurityToken sent with the message. AES by default;"
+                            + " Shiro also ships a Blowfish one.")
     private CipherService cipherService;
+    @Metadata(label = "security", security = "secret",
+              description = "The key the ShiroSecurityToken sent with the 
message is decrypted with.")
     private byte[] passPhrase;
+    @Metadata(description = "The Shiro security manager that authenticates and 
authorizes the caller. The constructors build"
+                            + " it from a Shiro INI resource; set it when the 
security manager comes from elsewhere.")
     private SecurityManager securityManager;
+    @Metadata(description = "The permissions an authenticated caller needs to 
continue on the route. Any one of them is"
+                            + " enough unless allPermissionsRequired is set. 
When they are set, rolesList is not checked.")
     private List<Permission> permissionsList;
+    @Metadata(description = "The roles an authenticated caller needs to 
continue on the route, checked only when"
+                            + " permissionsList is empty. Any one of them is 
enough unless allRolesRequired is set.")
     private List<String> rolesList;
+    @Metadata(defaultValue = "true",
+              description = "Whether to log the caller out after every 
exchange. When false, the authenticated subject"
+                            + " stays bound to the worker thread for the next 
exchange on that thread; the token of every"
+                            + " exchange is still authenticated.")
     private boolean alwaysReauthenticate;
+    @Metadata(defaultValue = "false",
+              description = "Whether the ShiroSecurityToken header is a base64 
string rather than the token object, which lets"
+                            + " it travel over JMS and other transports.")
     private boolean base64;
+    @Metadata(defaultValue = "false",
+              description = "Whether the caller needs all the permissions in 
permissionsList, rather than any one of them.")
     private boolean allPermissionsRequired;
+    @Metadata(defaultValue = "false",
+              description = "Whether the caller needs all the roles in 
rolesList, rather than any one of them.")
     private boolean allRolesRequired;
 
     public ShiroSecurityPolicy() {
diff --git 
a/components/camel-shiro/src/test/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyBindingTest.java
 
b/components/camel-shiro/src/test/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyBindingTest.java
new file mode 100644
index 000000000000..8cc92c9abcc1
--- /dev/null
+++ 
b/components/camel-shiro/src/test/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicyBindingTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.shiro.security;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Binds the policy the way a YAML {@code beans:} entry does, where every 
value arrives as a string.
+ */
+class ShiroSecurityPolicyBindingTest {
+
+    @Test
+    void bindsTheOptionsAsAYamlBeanDefinitionGivesThem() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("passPhrase", "CamelSecureRoute");
+            properties.put("alwaysReauthenticate", "false");
+            properties.put("base64", "true");
+            properties.put("allRolesRequired", "true");
+            properties.put("rolesList[0]", "admin");
+            properties.put("rolesList[1]", "auditor");
+
+            ShiroSecurityPolicy policy = new 
ShiroSecurityPolicy("classpath:securityconfig.ini");
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            // the generated configurer binds the options, not reflection: it 
is found by the class name
+            PropertyConfigurer configurer = 
PluginHelper.getConfigurerResolver(context)
+                    
.resolvePropertyConfigurer(ShiroSecurityPolicy.class.getName(), context);
+            assertNotNull(configurer);
+            assertEquals("ShiroSecurityPolicyConfigurer", 
configurer.getClass().getSimpleName());
+
+            
assertArrayEquals("CamelSecureRoute".getBytes(StandardCharsets.UTF_8), 
policy.getPassPhrase());
+            assertFalse(policy.isAlwaysReauthenticate());
+            assertTrue(policy.isBase64());
+            assertTrue(policy.isAllRolesRequired());
+            assertFalse(policy.isAllPermissionsRequired());
+            assertEquals(List.of("admin", "auditor"), policy.getRolesList());
+        }
+    }
+
+    @Test
+    void reauthenticatesByDefault() {
+        // the catalog documents the default the constructor sets, not the 
field's
+        assertTrue(new ShiroSecurityPolicy().isAlwaysReauthenticate());
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
index 618571997e25..fd413175aaf4 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -646,6 +646,8 @@ public final class ArtifactUtils {
                     
"org.apache.camel.support.KeyValueAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-support"),
             Map.<String, String> entry("KeyValueIdempotentRepository",
                     
"org.apache.camel.support.KeyValueIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-support"),
+            Map.<String, String> entry("KeycloakSecurityPolicy",
+                    
"org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-keycloak"),
             Map.<String, String> entry("KubernetesClusterService",
                     
"org.apache.camel.component.kubernetes.cluster.KubernetesClusterService|org.apache.camel.cluster.CamelClusterService|camel-kubernetes"),
             Map.<String, String> entry("LoggingHttpActivityListener",
@@ -670,6 +672,8 @@ public final class ArtifactUtils {
                     
"org.apache.camel.component.redis.processor.aggregate.RedisAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-redis"),
             Map.<String, String> entry("RedisKeyValueRepository",
                     
"org.apache.camel.component.redis.RedisKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-redis"),
+            Map.<String, String> entry("ShiroSecurityPolicy",
+                    
"org.apache.camel.component.shiro.security.ShiroSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-shiro"),
             Map.<String, String> entry("SimpleScheduledRoutePolicy",
                     
"org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-quartz"),
             Map.<String, String> entry("SpiffeSSLContextParameters",

Reply via email to