This recipe will show how using the Spring Security framework you can authenticate credentials passed into a route on an exchange, and determine whether that user/system (Principal in security terms) is authorized to access the route based on their role.
The Java code for this recipe is located in the org.camelcookbook.security.springsecurity
package. The Spring XML files are located under src/main/resources/META-INF/spring
and prefixed with springSecurity
.
To use Camel's Spring Security Component, add the following to the dependencies
section of your Maven POM:
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring-security</artifactId> <version>${camel-version}</version> </dependency>
In order for your configuration to be parsed correctly, you will also need the appropriate Spring Security JARs without any of their Core Spring dependencies in order to avoid version clashes.
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring-security-version}</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security-version}</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </exclusion> </exclusions> </dependency>
The ${spring-security-version}
used here is 3.1.4.RELEASE
. Yours should correspond to the same version used by the camel-spring-security
library.
The steps that we need to perform to authenticate and authorize an exchange on a route are as follows:
Policy
, and use it from the route.To carry out the steps described, you need to perform a series of actions as follows:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.org/schema/security" xmlns:camel-sec="http://camel.apache.org/schema/spring-security" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd http://camel.apache.org/schema/spring-security http://camel.apache.org/schema/spring-security/camel-spring-security.xsd">
Processor
:public class SecurityContextLoader implements Processor { @Override public void process(Exchange exchange) throws Exception { Message in = exchange.getIn(); String username = in.getHeader("username", String.class); String password = in.getHeader("password", String.class); Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, password); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } }
In this simple instance we expect the credentials to come down the route within the message headers. Credentials are stored in an implementation of org.springframework.security.core.Authentication
.
<bean id="securityContextLoader" class="org.camelcookbook.security.springsecurity.SecurityContextLoader"/>
<sec:user-service id="userService"> <sec:user name="jakub" password="supersecretpassword1" authorities="ROLE_USER, ROLE_ADMIN"/> <sec:user name="scott" password="supersecretpassword2" authorities="ROLE_USER"/> </sec:user-service>
A user service is one which, given a user name, fetches the corresponding details—it does not actually authenticate them. Spring Security provides out of the box implementations for accessing databases and LDAP servers, or you can build your own.
AuthenticationManager
that will authenticate the credentials using this user service:<sec:authentication-manager alias="authenticationManager"> <sec:authentication-provider user-service-ref="userService"/> </sec:authentication-manager>
Spring Security's authorization process works on the idea of voters (implementations of org.springframework.security.access.AccessDecisionVoter
) that each get to decide in turn whether the Authentication
object has access to the resource. Each voter votes to either grant or deny access, or abstain from the vote.
The access decision manager pulls together the votes, and makes the final decision. Three decision managers are available, which require that either any one voter votes yes (AffirmativeBased
), all voters vote yes (UnanimousBased
), or the majority of voters vote yes (ConcensusBased
).
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> <constructor-arg> <list> <bean class="org.springframework.security.access.vote.RoleVoter"/> </list> </constructor-arg> </bean>
The sole voter used here makes a decision based on roles.
ROLE_ADMIN
role:<camel-sec:authorizationPolicy id="adminAuthPolicy" access="ROLE_ADMIN" authenticationManager="authenticationManager" accessDecisionManager="accessDecisionManager" useThreadSecurityContext="true"/>
<from uri="direct:in"/> <process ref="securityContextLoader"/> <policy ref="adminAuthPolicy"> <to uri="mock:secure"/> </policy>
The process for granting access is to:
If either of the preceding steps 2 or 3 fail, an org.apache.camel.CamelAuthorizationException
will be thrown detailing the problem. This exception can be caught and handled as usual using the mechanism described in the Catching exceptions, and Fine-grained error handling using doTry…doCatch recipes in Chapter 7, Error Handling and Compensation.
The SecurityContextHolder
used to carry the user credentials relies on a ThreadLocal
internally. This is a design decision stemming from Spring Security's original use within web applications. In the context of integrations with Camel, it occasionally causes headaches, as the Authentication
object is lost when the exchange crosses a thread boundary, such as when being passed across a seda:
endpoint as discussed in the Asynchronously connecting routes recipe in Chapter 1, Structuring Routes. In general, you should extract the credentials from the transport and verify that they have access to the operation being performed within the same thread.
If this is not possible, you can get around this constraint by wrapping the Authentication
in a javax.security.auth.Subject
and setting it in the CamelAuthentication
header (defined as a constant in Exchange.AUTHENTICATION
) of the exchange. Camel's Spring Security policy
will extract it from the header and pass it into the authentication and access decision managers.
To do this, you need only to change your SecurityContextLoader
implementation to perform the following steps instead of initializing the SecurityContextHolder
:
Subject subject = new Subject();
subject.getPrincipals().add(authenticationToken);
in.setHeader(Exchange.AUTHENTICATION, subject);
Spring Security breaks down authentication and authorization into separate processes that are configured independently of each other. There is a substantial number of options available. For full details you should refer to the Spring Security documentation.
The Spring Security mechanisms are easy to use within Spring applications. They are much more difficult to use within OSGi Blueprints and plain Java applications, due to the dependency on Spring lifecycle interfaces and the amount of code that is hidden away from you within the custom Spring namespace handlers.
If you require authentication and authorization in one of these environments, you should consider the Apache Shiro security project as an alternative. This is accessible through the Camel Shiro component.
18.118.144.248