| /** |
| * 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()); |
| } |
| |
| } |