CAMEL-6424: camel-netty-http added support for basic auth. Work in progress.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/35c4f829 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/35c4f829 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/35c4f829 Branch: refs/heads/master Commit: 35c4f82968a18b9bbd105740ccfe3ad853599259 Parents: 4fbc8a7 Author: Claus Ibsen <davscl...@apache.org> Authored: Mon Jul 15 15:18:27 2013 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Mon Jul 15 15:18:27 2013 +0200 ---------------------------------------------------------------------- .../netty/http/JAASSecurityAuthenticator.java | 2 +- .../netty/http/NettyHttpComponent.java | 20 +++-- .../http/NettyHttpSecurityConfiguration.java | 26 ++++-- .../netty/http/SecurityAuthenticator.java | 5 +- .../http/handlers/HttpServerChannelHandler.java | 17 +++- .../NettyHttpBasicAuthConstraintMapperTest.java | 2 +- ...asicAuthCustomSecurityAuthenticatorTest.java | 93 ++++++++++++++++++++ .../netty/http/NettyHttpBasicAuthTest.java | 13 +++ ...HttpSimpleBasicAuthConstraintMapperTest.java | 88 ++++++++++++++++++ .../http/NettyHttpSimpleBasicAuthTest.java | 2 +- 10 files changed, 245 insertions(+), 23 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/JAASSecurityAuthenticator.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/JAASSecurityAuthenticator.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/JAASSecurityAuthenticator.java index 8fb4c85..a34f16b 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/JAASSecurityAuthenticator.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/JAASSecurityAuthenticator.java @@ -49,7 +49,7 @@ public class JAASSecurityAuthenticator implements SecurityAuthenticator { @Override public Subject login(HttpPrincipal principal) throws LoginException { if (ObjectHelper.isEmpty(getName())) { - throw new LoginException("Realm has not been configured on this SecurityAuthenticator: " + this); + throw new IllegalArgumentException("Realm has not been configured on this SecurityAuthenticator: " + this); } LOG.debug("Login username: {} using realm: {}", principal.getName(), getName()); http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java index 1f0fcd2..f6f4a9a 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java @@ -75,14 +75,7 @@ public class NettyHttpComponent extends NettyComponent implements HeaderFilterSt // any custom security configuration NettyHttpSecurityConfiguration securityConfiguration = resolveAndRemoveReferenceParameter(parameters, "securityConfiguration", NettyHttpSecurityConfiguration.class); - String realm = getAndRemoveParameter(parameters, "realm", String.class); - if (securityConfiguration != null && realm != null) { - throw new IllegalArgumentException("Cannot have both realm and securityConfiguration options configured"); - } else if (realm != null) { - // use default security configuration with the given realm, as a very easy way of enabling this - securityConfiguration = new NettyHttpSecurityConfiguration(); - securityConfiguration.setRealm(realm); - } + Map<String, Object> securityOptions = IntrospectionSupport.extractProperties(parameters, "securityConfiguration."); config = parseConfiguration(config, remaining, parameters); @@ -121,6 +114,17 @@ public class NettyHttpComponent extends NettyComponent implements HeaderFilterSt answer.setSecurityConfiguration(getSecurityConfiguration()); } + // configure any security options + if (securityOptions != null && !securityOptions.isEmpty()) { + securityConfiguration = answer.getSecurityConfiguration(); + if (securityConfiguration == null) { + securityConfiguration = new NettyHttpSecurityConfiguration(); + answer.setSecurityConfiguration(securityConfiguration); + } + setProperties(securityConfiguration, securityOptions); + validateParameters(uri, securityOptions, null); + } + answer.setNettySharedHttpServer(shared); return answer; } http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java index 4beffa9..ecc650f 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java @@ -16,6 +16,8 @@ */ package org.apache.camel.component.netty.http; +import org.apache.camel.LoggingLevel; + /** * Security configuration for the {@link NettyHttpConsumer}. */ @@ -24,8 +26,9 @@ public class NettyHttpSecurityConfiguration { private boolean authenticate = true; private String constraint = "Basic"; private String realm; - private ContextPathMatcher contextPathMatcher; + private ContextPathMatcher constraintMapping; private SecurityAuthenticator securityAuthenticator; + private LoggingLevel loginDeniedLoggingLevel = LoggingLevel.DEBUG; public boolean isAuthenticate() { return authenticate; @@ -64,8 +67,8 @@ public class NettyHttpSecurityConfiguration { this.realm = realm; } - public ContextPathMatcher getContextPathMatcher() { - return contextPathMatcher; + public ContextPathMatcher getConstraintMapping() { + return constraintMapping; } /** @@ -73,8 +76,8 @@ public class NettyHttpSecurityConfiguration { * <p/> * By default this is <tt>null</tt>, which means all resources is restricted. */ - public void setContextPathMatcher(ContextPathMatcher contextPathMatcher) { - this.contextPathMatcher = contextPathMatcher; + public void setConstraintMapping(ContextPathMatcher constraintMapping) { + this.constraintMapping = constraintMapping; } public SecurityAuthenticator getSecurityAuthenticator() { @@ -87,4 +90,17 @@ public class NettyHttpSecurityConfiguration { public void setSecurityAuthenticator(SecurityAuthenticator securityAuthenticator) { this.securityAuthenticator = securityAuthenticator; } + + public LoggingLevel getLoginDeniedLoggingLevel() { + return loginDeniedLoggingLevel; + } + + /** + * Sets a logging level to use for logging denied login attempts (incl stacktraces) + * <p/> + * This level is by default DEBUG. + */ + public void setLoginDeniedLoggingLevel(LoggingLevel loginDeniedLoggingLevel) { + this.loginDeniedLoggingLevel = loginDeniedLoggingLevel; + } } http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/SecurityAuthenticator.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/SecurityAuthenticator.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/SecurityAuthenticator.java index 65a976b..1d903f6 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/SecurityAuthenticator.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/SecurityAuthenticator.java @@ -38,11 +38,10 @@ public interface SecurityAuthenticator { /** * Attempts to login the {@link java.security.Principal} on this realm. * <p/> - * The login is a success if no Exception is thrown. The implementation can return - * a {@link Subject} instance, but is not required to do so. + * The login is a success if no Exception is thrown, and a {@link Subject} is returned. * * @param principal the principal - * @return optional subject returned for successful login + * @return the subject for the logged in principal, must <b>not</b> be <tt>null</tt> * @throws LoginException is thrown if error logging in the {@link java.security.Principal} */ Subject login(HttpPrincipal principal) throws LoginException; http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java index 144fb82..4ddaf83 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java @@ -24,6 +24,7 @@ import javax.security.auth.Subject; import javax.security.auth.login.LoginException; import org.apache.camel.Exchange; +import org.apache.camel.LoggingLevel; import org.apache.camel.component.netty.NettyConsumer; import org.apache.camel.component.netty.NettyHelper; import org.apache.camel.component.netty.handlers.ServerChannelHandler; @@ -31,6 +32,7 @@ import org.apache.camel.component.netty.http.HttpPrincipal; import org.apache.camel.component.netty.http.NettyHttpConsumer; import org.apache.camel.component.netty.http.NettyHttpSecurityConfiguration; import org.apache.camel.component.netty.http.SecurityAuthenticator; +import org.apache.camel.util.CamelLogger; import org.apache.camel.util.ObjectHelper; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; @@ -126,11 +128,12 @@ public class HttpServerChannelHandler extends ServerChannelHandler { String target = uri.getPath(); // is it a restricted resource? - boolean restricted = security.getContextPathMatcher() == null || security.getContextPathMatcher().matches(target); + boolean restricted = security.getConstraintMapping() == null || security.getConstraintMapping().matches(target); if (restricted) { // basic auth subject HttpPrincipal principal = extractBasicAuthSubject(request); - boolean authenticated = principal != null && authenticate(security.getSecurityAuthenticator(), principal) != null; + boolean authenticated = principal != null + && authenticate(security.getSecurityAuthenticator(), security.getLoginDeniedLoggingLevel(), principal) != null; if (principal == null || !authenticated) { if (principal == null) { LOG.debug("Http Basic Auth required for resource: {}", url); @@ -193,8 +196,14 @@ public class HttpServerChannelHandler extends ServerChannelHandler { * @param principal the principal * @return <tt>true</tt> if username and password is valid, <tt>false</tt> if not */ - protected Subject authenticate(SecurityAuthenticator authenticator, HttpPrincipal principal) throws LoginException { - return authenticator.login(principal); + protected Subject authenticate(SecurityAuthenticator authenticator, LoggingLevel deniedLoggingLevel, HttpPrincipal principal) { + try { + return authenticator.login(principal); + } catch (LoginException e) { + CamelLogger logger = new CamelLogger(LOG, deniedLoggingLevel); + logger.log("Cannot login " + principal.getName() + " due " + e.getMessage(), e); + } + return null; } @Override http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintMapperTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintMapperTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintMapperTest.java index 141475b..3724db7 100644 --- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintMapperTest.java +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintMapperTest.java @@ -48,7 +48,7 @@ public class NettyHttpBasicAuthConstraintMapperTest extends BaseNettyTest { ConstraintMappingContextPathMatcher matcher = new ConstraintMappingContextPathMatcher(); matcher.addInclusion("/foo/*"); matcher.addExclusion("/foo/public/*"); - security.setContextPathMatcher(matcher); + security.setConstraintMapping(matcher); jndi.bind("mySecurityConfig", security); http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthCustomSecurityAuthenticatorTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthCustomSecurityAuthenticatorTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthCustomSecurityAuthenticatorTest.java new file mode 100644 index 0000000..aa9a795 --- /dev/null +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthCustomSecurityAuthenticatorTest.java @@ -0,0 +1,93 @@ +/** + * 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.netty.http; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.JndiRegistry; +import org.junit.Test; + +public class NettyHttpBasicAuthCustomSecurityAuthenticatorTest extends BaseNettyTest { + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry jndi = super.createRegistry(); + jndi.bind("myAuthenticator", new MyAuthenticator()); + return jndi; + } + + @Test + public void testBasicAuth() throws Exception { + try { + template.requestBody("netty-http:http://localhost:{{port}}/foo", "Hello World", String.class); + fail("Should send back 401"); + } catch (CamelExecutionException e) { + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(401, cause.getStatusCode()); + } + + getMockEndpoint("mock:input").expectedBodiesReceived("Hello World"); + + // username:password is scott:secret + String auth = "Basic c2NvdHQ6c2VjcmV0"; + String out = template.requestBodyAndHeader("netty-http:http://localhost:{{port}}/foo", "Hello World", "Authorization", auth, String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty-http:http://0.0.0.0:{{port}}/foo?securityConfiguration.realm=foo&securityConfiguration.securityAuthenticator=#myAuthenticator") + .to("mock:input") + .transform().constant("Bye World"); + } + }; + } + + private final class MyAuthenticator implements SecurityAuthenticator { + + public void setName(String name) { + // noop + } + + public String getName() { + return null; + } + + @Override + public Subject login(HttpPrincipal principal) throws LoginException { + if (!principal.getPassword().equalsIgnoreCase("secret")) { + throw new LoginException("Login denied"); + } + // login success so return a subject + return new Subject(); + } + + @Override + public void logout(Subject subject) throws LoginException { + // noop + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthTest.java index 1775084..ca328ce 100644 --- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthTest.java +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthTest.java @@ -70,6 +70,19 @@ public class NettyHttpBasicAuthTest extends BaseNettyTest { assertMockEndpointsSatisfied(); } + @Test + public void testInvalidCredentials() throws Exception { + // username:password is scott:typo + try { + // password is invalid so we should get a 401 + String auth = "Basic c2NvdHQ6dHlwbw=="; + template.requestBodyAndHeader("netty-http:http://localhost:{{port}}/foo", "Hello World", "Authorization", auth, String.class); + } catch (CamelExecutionException e) { + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(401, cause.getStatusCode()); + } + } + @Override protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthConstraintMapperTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthConstraintMapperTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthConstraintMapperTest.java new file mode 100644 index 0000000..9ecfc72 --- /dev/null +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthConstraintMapperTest.java @@ -0,0 +1,88 @@ +/** + * 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.netty.http; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.JndiRegistry; +import org.junit.Test; + +public class NettyHttpSimpleBasicAuthConstraintMapperTest extends BaseNettyTest { + + @Override + public void setUp() throws Exception { + System.setProperty("java.security.auth.login.config", "src/test/resources/myjaas.config"); + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + System.clearProperty("java.security.auth.login.config"); + super.tearDown(); + } + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry jndi = super.createRegistry(); + + ConstraintMappingContextPathMatcher matcher = new ConstraintMappingContextPathMatcher(); + matcher.addInclusion("/foo/*"); + matcher.addExclusion("/foo/public/*"); + + jndi.bind("myConstraint", matcher); + + return jndi; + } + + @Test + public void testBasicAuth() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("Hello Public", "Hello World"); + + // we dont need auth for the public page + String out = template.requestBody("netty-http:http://localhost:{{port}}/foo/public/hello.txt", "Hello Public", String.class); + assertEquals("Bye World", out); + + try { + template.requestBody("netty-http:http://localhost:{{port}}/foo", "Hello World", String.class); + fail("Should send back 401"); + } catch (CamelExecutionException e) { + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(401, cause.getStatusCode()); + } + + // username:password is scott:secret + String auth = "Basic c2NvdHQ6c2VjcmV0"; + out = template.requestBodyAndHeader("netty-http:http://localhost:{{port}}/foo", "Hello World", "Authorization", auth, String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty-http:http://0.0.0.0:{{port}}/foo?matchOnUriPrefix=true" + + "&securityConfiguration.realm=karaf&securityConfiguration.constraintMapping=#myConstraint") + .to("mock:input") + .transform().constant("Bye World"); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/35c4f829/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java index 0e7668f..358af4b 100644 --- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java @@ -59,7 +59,7 @@ public class NettyHttpSimpleBasicAuthTest extends BaseNettyTest { return new RouteBuilder() { @Override public void configure() throws Exception { - from("netty-http:http://0.0.0.0:{{port}}/foo?realm=karaf") + from("netty-http:http://0.0.0.0:{{port}}/foo?securityConfiguration.realm=karaf") .to("mock:input") .transform().constant("Bye World"); }