blob: bae76591f5e9a0deb7eca0fa10186dad7ff93fcd [file] [log] [blame]
/**
* 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());
}
}