diff --git a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityConstants.java b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityConstants.java index b3b30e800efac..4f8e7d21e5adf 100644 --- a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityConstants.java +++ b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityConstants.java @@ -21,9 +21,9 @@ */ public final class ShiroSecurityConstants { - public static final String SHIRO_SECURITY_TOKEN = "SHIRO_SECURITY_TOKEN"; - public static final String SHIRO_SECURITY_USERNAME = "SHIRO_SECURITY_USERNAME"; - public static final String SHIRO_SECURITY_PASSWORD = "SHIRO_SECURITY_PASSWORD"; + public static final String SHIRO_SECURITY_TOKEN = "CamelShiroSecurityToken"; + public static final String SHIRO_SECURITY_USERNAME = "CamelShiroSecurityUsername"; + public static final String SHIRO_SECURITY_PASSWORD = "CamelShiroSecurityPassword"; private ShiroSecurityConstants() { } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc index 78a7434455898..75b7aa87622b7 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc @@ -656,6 +656,71 @@ The generated Endpoint DSL header accessors on each component's +=== camel-shiro - potential breaking change + +The three Exchange header constants in `ShiroSecurityConstants` that drive +Shiro authentication used header values outside the `Camel` namespace +(`SHIRO_SECURITY_TOKEN`, `SHIRO_SECURITY_USERNAME`, `SHIRO_SECURITY_PASSWORD`) +and were therefore not filtered by the default `HeaderFilterStrategy`. They +have been renamed to follow the Camel naming convention. The Java field names +are unchanged; only the header string values have changed: + +[options="header"] +|=== +| Constant | Previous value | New value +| `ShiroSecurityConstants.SHIRO_SECURITY_TOKEN` | `SHIRO_SECURITY_TOKEN` | `CamelShiroSecurityToken` +| `ShiroSecurityConstants.SHIRO_SECURITY_USERNAME` | `SHIRO_SECURITY_USERNAME` | `CamelShiroSecurityUsername` +| `ShiroSecurityConstants.SHIRO_SECURITY_PASSWORD` | `SHIRO_SECURITY_PASSWORD` | `CamelShiroSecurityPassword` +|=== + +These headers carry credentials and a serialized authentication token, so +filtering them at transport boundaries by default is particularly important. + +Routes that reference the constants symbolically (for example +`setHeader(ShiroSecurityConstants.SHIRO_SECURITY_USERNAME, ...)`) continue to +work without changes. Routes that set the header by its literal string value +(for example `setHeader("SHIRO_SECURITY_USERNAME", ...)`) must be updated to +use the new value (`setHeader("CamelShiroSecurityUsername", ...)`). + +Because the three header values are now in the `Camel*` namespace, transports +that filter Camel-internal headers by default (JMS, CXF, HTTP, etc.) will +strip the serialized Shiro authentication token before publishing. This is +the intended behavior for untrusted producers. Trusted Shiro-over-transport +routes that previously relied on the token surviving the boundary must opt +those three headers back in via a custom `HeaderFilterStrategy`, for example: + +[source,java] +---- +public class ShiroFriendlyJmsHeaderFilterStrategy extends JmsHeaderFilterStrategy { + @Override + public boolean applyFilterToCamelHeaders(String name, Object value, Exchange ex) { + if (isShiroSecurityHeader(name)) { + return false; + } + return super.applyFilterToCamelHeaders(name, value, ex); + } + + @Override + public boolean applyFilterToExternalHeaders(String name, Object value, Exchange ex) { + if (isShiroSecurityHeader(name)) { + return false; + } + return super.applyFilterToExternalHeaders(name, value, ex); + } + + private static boolean isShiroSecurityHeader(String name) { + return ShiroSecurityConstants.SHIRO_SECURITY_TOKEN.equalsIgnoreCase(name) + || ShiroSecurityConstants.SHIRO_SECURITY_USERNAME.equalsIgnoreCase(name) + || ShiroSecurityConstants.SHIRO_SECURITY_PASSWORD.equalsIgnoreCase(name); + } +} + +jmsComponent.setHeaderFilterStrategy(new ShiroFriendlyJmsHeaderFilterStrategy()); +---- + +A worked example is in `ShiroOverJmsTest` in the `camel-itest` module. + + === camel-web3j - potential breaking change The Exchange header constants in `Web3jConstants` have been renamed to follow the diff --git a/tests/camel-itest/src/test/java/org/apache/camel/itest/shiro/ShiroOverJmsTest.java b/tests/camel-itest/src/test/java/org/apache/camel/itest/shiro/ShiroOverJmsTest.java index ccd5e8b2661f6..5a702f8e9da8d 100644 --- a/tests/camel-itest/src/test/java/org/apache/camel/itest/shiro/ShiroOverJmsTest.java +++ b/tests/camel-itest/src/test/java/org/apache/camel/itest/shiro/ShiroOverJmsTest.java @@ -19,8 +19,10 @@ import java.util.HashMap; import java.util.Map; +import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.jms.JmsComponent; +import org.apache.camel.component.jms.JmsHeaderFilterStrategy; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.component.shiro.security.ShiroSecurityConstants; import org.apache.camel.component.shiro.security.ShiroSecurityPolicy; @@ -61,9 +63,42 @@ protected void bindToRegistry(Registry registry) { amq.setCamelContext(context); + // After CAMEL-23592 the Shiro security headers live in the CamelShiroSecurity* namespace, + // which the default JmsHeaderFilterStrategy filters at the transport boundary. For a + // trusted Shiro-over-JMS route, the operator must opt those three headers back in. + amq.setHeaderFilterStrategy(new ShiroFriendlyJmsHeaderFilterStrategy()); + registry.bind("jms", amq); } + /** + * Allows the three Shiro security headers (token, username, password) to cross the JMS transport boundary while + * still filtering every other Camel-internal header. + */ + private static final class ShiroFriendlyJmsHeaderFilterStrategy extends JmsHeaderFilterStrategy { + @Override + public boolean applyFilterToCamelHeaders(String headerName, Object headerValue, Exchange exchange) { + if (isShiroSecurityHeader(headerName)) { + return false; + } + return super.applyFilterToCamelHeaders(headerName, headerValue, exchange); + } + + @Override + public boolean applyFilterToExternalHeaders(String headerName, Object headerValue, Exchange exchange) { + if (isShiroSecurityHeader(headerName)) { + return false; + } + return super.applyFilterToExternalHeaders(headerName, headerValue, exchange); + } + + private static boolean isShiroSecurityHeader(String headerName) { + return ShiroSecurityConstants.SHIRO_SECURITY_TOKEN.equalsIgnoreCase(headerName) + || ShiroSecurityConstants.SHIRO_SECURITY_USERNAME.equalsIgnoreCase(headerName) + || ShiroSecurityConstants.SHIRO_SECURITY_PASSWORD.equalsIgnoreCase(headerName); + } + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() {