/** * 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.CamelExchangeException; 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.camel.util.ObjectHelper; 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 Logger LOG = LoggerFactory.getLogger(ShiroSecurityProcessor.class); private final ShiroSecurityPolicy policy; public ShiroSecurityProcessor(Processor processor, ShiroSecurityPolicy policy) { super(processor); this.policy = policy; } @Override public boolean process(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.process(exchange, callback); } private void applySecurityPolicy(Exchange exchange) throws Exception { ByteSource encryptedToken; // if we have username and password as headers then use them to create a token String username = exchange.getIn().getHeader(ShiroSecurityConstants.SHIRO_SECURITY_USERNAME, String.class); String password = exchange.getIn().getHeader(ShiroSecurityConstants.SHIRO_SECURITY_PASSWORD, String.class); if (username != null && password != null) { ShiroSecurityToken token = new ShiroSecurityToken(username, password); // store the token as header, either as base64 or as the object as-is if (policy.isBase64()) { ByteSource bytes = ShiroSecurityHelper.encrypt(token, policy.getPassPhrase(), policy.getCipherService()); String base64 = bytes.toBase64(); exchange.getIn().setHeader(ShiroSecurityConstants.SHIRO_SECURITY_TOKEN, base64); } else { exchange.getIn().setHeader(ShiroSecurityConstants.SHIRO_SECURITY_TOKEN, token); } // and now remove the headers as we turned those into the token instead exchange.getIn().removeHeader(ShiroSecurityConstants.SHIRO_SECURITY_USERNAME); exchange.getIn().removeHeader(ShiroSecurityConstants.SHIRO_SECURITY_PASSWORD); } Object token = ExchangeHelper.getMandatoryHeader(exchange, ShiroSecurityConstants.SHIRO_SECURITY_TOKEN, Object.class); // we support the token in a number of ways if (token instanceof ShiroSecurityToken) { ShiroSecurityToken sst = (ShiroSecurityToken) token; encryptedToken = ShiroSecurityHelper.encrypt(sst, policy.getPassPhrase(), policy.getCipherService()); // Remove unencrypted token + replace with an encrypted token exchange.getIn().removeHeader(ShiroSecurityConstants.SHIRO_SECURITY_TOKEN); exchange.getIn().setHeader(ShiroSecurityConstants.SHIRO_SECURITY_TOKEN, encryptedToken); } else if (token instanceof String) { String data = (String) token; if (policy.isBase64()) { byte[] bytes = Base64.decode(data); encryptedToken = ByteSource.Util.bytes(bytes); } else { encryptedToken = ByteSource.Util.bytes(data); } } else if (token instanceof ByteSource) { encryptedToken = (ByteSource) token; } else { throw new CamelExchangeException("Shiro security header " + ShiroSecurityConstants.SHIRO_SECURITY_TOKEN + " is unsupported type: " + ObjectHelper.classCanonicalName(token), exchange); } 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()) { if (policy.isAllPermissionsRequired()) { authorized = currentUser.isPermittedAll(policy.getPermissionsList()); } else { for (Permission permission : policy.getPermissionsList()) { if (currentUser.isPermitted(permission)) { authorized = true; break; } } } } else if (!policy.getRolesList().isEmpty()) { if (policy.isAllRolesRequired()) { authorized = currentUser.hasAllRoles(policy.getRolesList()); } else { for (String role : policy.getRolesList()) { if (currentUser.hasRole(role)) { authorized = true; break; } } } } else { LOG.trace("Valid Permissions or Roles 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 roles or permissions to perform further processing.", exchange); } LOG.debug("Current user {} is successfully authorized.", currentUser.getPrincipal()); } }