Updated Branches: refs/heads/master 17b2306d9 -> b395401c2
CAMEL-6354: Polished camel-shiro Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/f3d14298 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/f3d14298 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/f3d14298 Branch: refs/heads/master Commit: f3d14298d4914525f2ac4e9a79216a2c0638e668 Parents: 17b2306 Author: Claus Ibsen <[email protected]> Authored: Sun May 12 13:17:27 2013 +0200 Committer: Claus Ibsen <[email protected]> Committed: Sun May 12 13:24:14 2013 +0200 ---------------------------------------------------------------------- .../shiro/security/ShiroSecurityHelper.java | 53 +++++ .../shiro/security/ShiroSecurityPolicy.java | 154 +-------------- .../shiro/security/ShiroSecurityProcessor.java | 156 +++++++++++++++ .../shiro/security/ShiroSecurityTokenInjector.java | 24 +-- .../src/test/resources/log4j.properties | 21 ++- 5 files changed, 235 insertions(+), 173 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/f3d14298/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityHelper.java ---------------------------------------------------------------------- diff --git a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityHelper.java b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityHelper.java new file mode 100644 index 0000000..1f64723 --- /dev/null +++ b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityHelper.java @@ -0,0 +1,53 @@ +/** + * 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; + +import org.apache.camel.util.IOHelper; +import org.apache.shiro.crypto.CipherService; +import org.apache.shiro.util.ByteSource; + +public final class ShiroSecurityHelper { + + private ShiroSecurityHelper() { + } + + public static ByteSource encrypt(ShiroSecurityToken securityToken, byte[] passPhrase, CipherService cipherService) throws Exception { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + ObjectOutput serialStream = new ObjectOutputStream(stream); + try { + serialStream.writeObject(securityToken); + return cipherService.encrypt(stream.toByteArray(), passPhrase); + } finally { + close(serialStream); + IOHelper.close(stream); + } + } + + private static void close(ObjectOutput output) { + try { + output.close(); + } catch (IOException e) { + // ignore + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f3d14298/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityPolicy.java ---------------------------------------------------------------------- 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 b0e6b51..4e8fff5 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 @@ -16,44 +16,26 @@ */ package org.apache.camel.component.shiro.security; -import java.io.ByteArrayInputStream; -import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.List; -import org.apache.camel.AsyncCallback; -import org.apache.camel.AsyncProcessor; -import org.apache.camel.CamelAuthorizationException; -import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.spi.AuthorizationPolicy; import org.apache.camel.spi.RouteContext; -import org.apache.camel.util.AsyncProcessorConverterHelper; -import org.apache.camel.util.AsyncProcessorHelper; -import org.apache.camel.util.ExchangeHelper; -import org.apache.camel.util.IOHelper; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.IncorrectCredentialsException; -import org.apache.shiro.authc.LockedAccountException; -import org.apache.shiro.authc.UnknownAccountException; -import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.Permission; -import org.apache.shiro.codec.Base64; import org.apache.shiro.config.Ini; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.crypto.CipherService; import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.util.ByteSource; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroSecurityPolicy implements AuthorizationPolicy { - private static final transient Logger LOG = LoggerFactory.getLogger(ShiroSecurityPolicy.class); + private static final transient Logger LOG = LoggerFactory.getLogger(ShiroSecurityPolicy.class); private final byte[] bits128 = { (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F, @@ -119,138 +101,16 @@ public class ShiroSecurityPolicy implements AuthorizationPolicy { } public void beforeWrap(RouteContext routeContext, ProcessorDefinition<?> definition) { - //Not implemented + // noop } - public Processor wrap(RouteContext routeContext, final Processor processor) { - return new AsyncProcessor() { - public boolean process(Exchange exchange, final AsyncCallback callback) { - boolean sync; - try { - applySecurityPolicy(exchange); - } catch (Exception e) { - // exception occurred so break out - exchange.setException(e); - callback.done(true); - return true; - } - - // If here, then user is authenticated and authorized - // Now let the original processor continue routing supporting the async routing engine - AsyncProcessor ap = AsyncProcessorConverterHelper.convert(processor); - sync = AsyncProcessorHelper.process(ap, exchange, new AsyncCallback() { - public void done(boolean doneSync) { - // we only have to handle async completion of this policy - if (doneSync) { - return; - } - callback.done(false); - } - }); - - if (!sync) { - // if async, continue routing async - return false; - } - - // we are done synchronously, so do our after work and invoke the callback - callback.done(true); - return true; - } - - public void process(Exchange exchange) throws Exception { - applySecurityPolicy(exchange); - processor.process(exchange); - } - - private void applySecurityPolicy(Exchange exchange) throws Exception { - ByteSource encryptedToken; - if (isBase64()) { - String base64 = ExchangeHelper.getMandatoryHeader(exchange, ShiroConstants.SHIRO_SECURITY_TOKEN, String.class); - byte[] bytes = Base64.decode(base64); - encryptedToken = ByteSource.Util.bytes(bytes); - } else { - encryptedToken = ExchangeHelper.getMandatoryHeader(exchange, ShiroConstants.SHIRO_SECURITY_TOKEN, ByteSource.class); - } - - ByteSource decryptedToken = getCipherService().decrypt(encryptedToken.getBytes(), getPassPhrase()); - - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedToken.getBytes()); - ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); - ShiroSecurityToken securityToken; - try { - securityToken = (ShiroSecurityToken)objectInputStream.readObject(); - } finally { - IOHelper.close(objectInputStream, byteArrayInputStream); - } - - Subject currentUser = SecurityUtils.getSubject(); - - // Authenticate user if not authenticated - try { - authenticateUser(currentUser, securityToken); - - // Test whether user's role is authorized to perform functions in the permissions list - authorizeUser(currentUser, exchange); - } finally { - if (alwaysReauthenticate) { - currentUser.logout(); - } - } - } - }; - } - - private void authenticateUser(Subject currentUser, ShiroSecurityToken securityToken) { - boolean authenticated = currentUser.isAuthenticated(); - boolean sameUser = securityToken.getUsername().equals(currentUser.getPrincipal()); - LOG.debug("Authenticated: {}, same Username: {}", authenticated, sameUser); - - if (!authenticated || !sameUser) { - UsernamePasswordToken token = new UsernamePasswordToken(securityToken.getUsername(), securityToken.getPassword()); - if (alwaysReauthenticate) { - token.setRememberMe(false); - } else { - token.setRememberMe(true); - } - - try { - currentUser.login(token); - LOG.debug("Current User {} successfully authenticated", currentUser.getPrincipal()); - } catch (UnknownAccountException uae) { - throw new UnknownAccountException("Authentication Failed. There is no user with username of " + token.getPrincipal(), uae.getCause()); - } catch (IncorrectCredentialsException ice) { - throw new IncorrectCredentialsException("Authentication Failed. Password for account " + token.getPrincipal() + " was incorrect!", ice.getCause()); - } catch (LockedAccountException lae) { - throw new LockedAccountException("Authentication Failed. The account for username " + token.getPrincipal() + " is locked." - + "Please contact your administrator to unlock it.", lae.getCause()); - } catch (AuthenticationException ae) { - throw new AuthenticationException("Authentication Failed.", ae.getCause()); - } - } - } - - private void authorizeUser(Subject currentUser, Exchange exchange) throws CamelAuthorizationException { - boolean authorized = false; - if (!permissionsList.isEmpty()) { - for (Permission permission : permissionsList) { - if (currentUser.isPermitted(permission)) { - authorized = true; - break; - } - } - } else { - LOG.debug("Valid Permissions List not specified for ShiroSecurityPolicy. No authorization checks will be performed for current user"); - authorized = true; + public Processor wrap(RouteContext routeContext, final Processor processor) { + if (LOG.isDebugEnabled()) { + LOG.debug("Securing route {} using Shiro policy {}", routeContext.getRoute().getId(), this); } - - if (!authorized) { - throw new CamelAuthorizationException("Authorization Failed. Subject's role set does not have the necessary permissions to perform further processing", exchange); - } - - LOG.debug("Current User {} is successfully authorized. The exchange will be allowed to proceed", currentUser.getPrincipal()); + return new ShiroSecurityProcessor(processor, this); } - + public CipherService getCipherService() { return cipherService; } http://git-wip-us.apache.org/repos/asf/camel/blob/f3d14298/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityProcessor.java ---------------------------------------------------------------------- diff --git a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityProcessor.java b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityProcessor.java new file mode 100644 index 0000000..d353234 --- /dev/null +++ b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityProcessor.java @@ -0,0 +1,156 @@ +/** + * 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.io.ByteArrayInputStream; +import java.io.ObjectInputStream; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.CamelAuthorizationException; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.processor.DelegateAsyncProcessor; +import org.apache.camel.util.ExchangeHelper; +import org.apache.camel.util.IOHelper; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.IncorrectCredentialsException; +import org.apache.shiro.authc.LockedAccountException; +import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.Permission; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ByteSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link Processor} that executes the authentication and authorization of the {@link Subject} accordingly + * to the {@link ShiroSecurityPolicy}. + */ +public class ShiroSecurityProcessor extends DelegateAsyncProcessor { + + private static final transient Logger LOG = LoggerFactory.getLogger(ShiroSecurityProcessor.class); + private final ShiroSecurityPolicy policy; + + public ShiroSecurityProcessor(Processor processor, ShiroSecurityPolicy policy) { + super(processor); + this.policy = policy; + } + + @Override + protected boolean processNext(Exchange exchange, AsyncCallback callback) { + try { + applySecurityPolicy(exchange); + } catch (Exception e) { + // exception occurred so break out + exchange.setException(e); + callback.done(true); + return true; + } + + return super.processNext(exchange, callback); + } + + private void applySecurityPolicy(Exchange exchange) throws Exception { + ByteSource encryptedToken; + if (policy.isBase64()) { + String base64 = ExchangeHelper.getMandatoryHeader(exchange, ShiroConstants.SHIRO_SECURITY_TOKEN, String.class); + byte[] bytes = Base64.decode(base64); + encryptedToken = ByteSource.Util.bytes(bytes); + } else { + encryptedToken = ExchangeHelper.getMandatoryHeader(exchange, ShiroConstants.SHIRO_SECURITY_TOKEN, ByteSource.class); + } + + ByteSource decryptedToken = policy.getCipherService().decrypt(encryptedToken.getBytes(), policy.getPassPhrase()); + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedToken.getBytes()); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + ShiroSecurityToken securityToken; + try { + securityToken = (ShiroSecurityToken)objectInputStream.readObject(); + } finally { + IOHelper.close(objectInputStream, byteArrayInputStream); + } + + Subject currentUser = SecurityUtils.getSubject(); + + // Authenticate user if not authenticated + try { + authenticateUser(currentUser, securityToken); + + // Test whether user's role is authorized to perform functions in the permissions list + authorizeUser(currentUser, exchange); + } finally { + if (policy.isAlwaysReauthenticate()) { + currentUser.logout(); + } + } + } + + private void authenticateUser(Subject currentUser, ShiroSecurityToken securityToken) { + boolean authenticated = currentUser.isAuthenticated(); + boolean sameUser = securityToken.getUsername().equals(currentUser.getPrincipal()); + LOG.trace("Authenticated: {}, same Username: {}", authenticated, sameUser); + + if (!authenticated || !sameUser) { + UsernamePasswordToken token = new UsernamePasswordToken(securityToken.getUsername(), securityToken.getPassword()); + if (policy.isAlwaysReauthenticate()) { + token.setRememberMe(false); + } else { + token.setRememberMe(true); + } + + try { + currentUser.login(token); + LOG.debug("Current user {} successfully authenticated", currentUser.getPrincipal()); + } catch (UnknownAccountException uae) { + throw new UnknownAccountException("Authentication Failed. There is no user with username of " + token.getPrincipal(), uae.getCause()); + } catch (IncorrectCredentialsException ice) { + throw new IncorrectCredentialsException("Authentication Failed. Password for account " + token.getPrincipal() + " was incorrect!", ice.getCause()); + } catch (LockedAccountException lae) { + throw new LockedAccountException("Authentication Failed. The account for username " + token.getPrincipal() + " is locked." + + "Please contact your administrator to unlock it.", lae.getCause()); + } catch (AuthenticationException ae) { + throw new AuthenticationException("Authentication Failed.", ae.getCause()); + } + } + } + + private void authorizeUser(Subject currentUser, Exchange exchange) throws CamelAuthorizationException { + boolean authorized = false; + if (!policy.getPermissionsList().isEmpty()) { + for (Permission permission : policy.getPermissionsList()) { + if (currentUser.isPermitted(permission)) { + authorized = true; + break; + } + } + } else { + LOG.trace("Valid Permissions List not specified for ShiroSecurityPolicy. No authorization checks will be performed for current user."); + authorized = true; + } + + if (!authorized) { + throw new CamelAuthorizationException("Authorization Failed. Subject's role set does not have the necessary permissions to perform further processing.", exchange); + } + + LOG.debug("Current user {} is successfully authorized.", currentUser.getPrincipal()); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f3d14298/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityTokenInjector.java ---------------------------------------------------------------------- diff --git a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityTokenInjector.java b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityTokenInjector.java index e01eb66..cf1d2ea 100644 --- a/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityTokenInjector.java +++ b/components/camel-shiro/src/main/java/org/apache/camel/component/shiro/security/ShiroSecurityTokenInjector.java @@ -16,14 +16,8 @@ */ package org.apache.camel.component.shiro.security; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutput; -import java.io.ObjectOutputStream; - import org.apache.camel.Exchange; import org.apache.camel.Processor; -import org.apache.camel.util.IOHelper; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.crypto.CipherService; import org.apache.shiro.util.ByteSource; @@ -58,15 +52,7 @@ public class ShiroSecurityTokenInjector implements Processor { } public ByteSource encrypt() throws Exception { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - ObjectOutput serialStream = new ObjectOutputStream(stream); - try { - serialStream.writeObject(securityToken); - return cipherService.encrypt(stream.toByteArray(), passPhrase); - } finally { - close(serialStream); - IOHelper.close(stream); - } + return ShiroSecurityHelper.encrypt(securityToken, passPhrase, cipherService); } public void process(Exchange exchange) throws Exception { @@ -114,12 +100,4 @@ public class ShiroSecurityTokenInjector implements Processor { this.base64 = base64; } - private static void close(ObjectOutput output) { - try { - output.close(); - } catch (IOException e) { - // ignore - } - } - } http://git-wip-us.apache.org/repos/asf/camel/blob/f3d14298/components/camel-shiro/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/components/camel-shiro/src/test/resources/log4j.properties b/components/camel-shiro/src/test/resources/log4j.properties index 7153e32..baf0bb6 100644 --- a/components/camel-shiro/src/test/resources/log4j.properties +++ b/components/camel-shiro/src/test/resources/log4j.properties @@ -1,11 +1,26 @@ +## ------------------------------------------------------------------------ +## 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. +## ------------------------------------------------------------------------ # -# The logging properties used for eclipse testing, We want to see debug output on the console. +# The logging properties used for testing. # log4j.rootLogger=INFO, file -log4j.logger.org.springframework=WARN -#log4j.logger.org.apache.camel=DEBUG +log4j.logger.org.apache.camel.component.shiro.security=DEBUG # CONSOLE appender not used by default log4j.appender.out=org.apache.log4j.ConsoleAppender
