简体   繁体   中英

Spring Integration: how to make the SecurityContext propagation working?

I'm trying to integrate two simple Spring Web Applications with Spring Integration, and I want to propagate the Spring SecurityContext between the two applications, but I've not yet found a working solution.

The two applications are very similar between each other, there are only a few differences in the configuration: one of them has to be the "caller", the other one has to be the "receiver".

In order to getting the expected result, I'm using Spring Security and I've configured both applications with the following Spring Security configuration:

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.1.xsd">

    <http auto-config="true">
        <intercept-url pattern="/" access="permitAll" />
        <intercept-url pattern="/service/**" access="permitAll" />
        <intercept-url pattern="/home" access="permitAll" />
        <intercept-url pattern="/admin**" access="hasRole('ADMIN')" />
        <intercept-url pattern="/dba**" access="hasRole('ADMIN') and hasRole('DBA')" />
        <form-login  authentication-failure-url="/accessDenied" />
    </http>

    <authentication-manager id="authenticationManager">
        <authentication-provider>
            <user-service>
                <user name="user"  password="user"  authorities="ROLE_USER" />
                <user name="admin" password="admin" authorities="ROLE_ADMIN" />
                <user name="dba"   password="dba" authorities="ROLE_ADMIN,ROLE_DBA" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <beans:constructor-arg>
            <beans:list>
                <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
                <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
            </beans:list>
        </beans:constructor-arg>
    </beans:bean>
</beans:beans>

This is working: if I go to the url http://localhost:8080/caller/login , the login process is correctly managed by the Spring Security layer.

So I'm trying to configure the Spring Integration module in order to "integrate" the two applications; my idea (wrong?) is to use an http:outbound-gateway from the "caller", that invoke the "receiver" at a specific URL. On the other side, there is an http:inbound-gateway that manage the requests.

Here is the configuration of the "caller" side:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:int-http="http://www.springframework.org/schema/integration/http"
    xmlns:task="http://www.springframework.org/schema/task"
    xmlns:int-security="http://www.springframework.org/schema/integration/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.3.xsd
        http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.3.xsd
        http://www.springframework.org/schema/integration/security http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd">

    <int:channel id="requestChannel">
        <int:dispatcher task-executor="executor"/>
    </int:channel>

    <int:channel-interceptor ref="requestChannelInterceptor" pattern="requestChannel">
    </int:channel-interceptor>

    <task:executor id="executor" pool-size="5"/>

    <bean id="requestChannelInterceptor" class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"></bean>

    <bean id="requestChannelBean" class="test.spring.webapp.client.MessagingChannel">
        <property name="requestChannel" ref="requestChannel"></property>
    </bean>

    <int-http:outbound-gateway id="gateway" request-channel="requestChannel"
        encode-uri="true" url="http://localhost:8080/receiver/service/{request}"
        http-method="GET" >
        <int-http:uri-variable name="request" expression="payload"/>
    </int-http:outbound-gateway>

    <int-security:secured-channels access-decision-manager="accessDecisionManager">
        <int-security:access-policy pattern="requestChannel" send-access="ROLE_ADMIN,ROLE_USER,ROLE_DBA,ROLE_ANONYMOUS"/>
    </int-security:secured-channels>
</beans>

Here is the configuration of the "receiver" side instead:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:int-http="http://www.springframework.org/schema/integration/http"
    xmlns:task="http://www.springframework.org/schema/task"
    xmlns:int-security="http://www.springframework.org/schema/integration/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.3.xsd
        http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.3.xsd
        http://www.springframework.org/schema/integration/security http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd">

    <int:channel id="requestChannel">
        <int:dispatcher task-executor="executor"/>
    </int:channel>

    <int:channel-interceptor ref="requestChannelInterceptor" pattern="requestChannel">
    </int:channel-interceptor>

    <task:executor id="executor" pool-size="5"/>

    <bean id="requestChannelInterceptor" class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"></bean>

    <int-http:inbound-gateway id="gateway" request-channel="requestChannel"
                  path="/service/**" 
                  supported-methods="GET">
    </int-http:inbound-gateway>

    <int-security:secured-channels access-decision-manager="accessDecisionManager">
        <int-security:access-policy pattern="requestChannel" send-access="ROLE_ADMIN,ROLE_USER,ROLE_DBA,ROLE_ANONYMOUS"/>
    </int-security:secured-channels>

    <bean id="channelService"
        class="test.spring.webapp.server.ChannelService"/> 

    <int:service-activator id="channelServiceActivator"
        ref="channelService"
        input-channel="requestChannel"
        method="manage"/>
</beans>

As you can see, I'm using the component SecurityContextPropagationChannelInterceptor of Spring Integration, that is intended to make the "propagation" of the security context.

I'm also using the int-security:secured-channels in the configuration, as illustrated in the documentation.

But this configuration doesn't work: when I call the URL http://localhost:8080/caller/service/hello (logged or not logged is the same), I'm getting the following Exception:

org.springframework.messaging.MessageHandlingException: HTTP request execution failed for URI [http://localhost:8080/receiver/service/hello]; 
    nested exception is org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error

Furthermore, the class org.springframework.integration.channel.interceptor.ThreadStatePropagationChannelInterceptor has the following method:

@Override
@SuppressWarnings("unchecked")
public final Message<?> postReceive(Message<?> message, MessageChannel channel) {
    if (message instanceof MessageWithThreadState) {
        MessageWithThreadState<S> messageWithThreadState = (MessageWithThreadState<S>) message;
        Message<?> messageToHandle = messageWithThreadState.message;
        populatePropagatedContext(messageWithThreadState.state, messageToHandle, channel);
        return messageToHandle;
    }
    return message;
}

The message instance contains the logged Principal. Debugging this method, I noticed that the logged Principal (for example "admin") is obtained only on the "caller" side, not in the "receiver" side (where there is always the "anonymousUser"): why the propagation of the SecurityContext is not working?

I believe that something is totally wrong or missing in my configuration...

It can't be propagated to the another process (application) by its premise. See the implementation of SecurityContextPropagationChannelInterceptor . It is based on ThreadStatePropagationChannelInterceptor . One more time Thread State , not one process to another via network.

Since there is no one common solution for inter-process SecurityContext transfer, there is no anything as out-of-the-box.

As I answered you in another your question ( Spring Integration: the SecurityContext propagation ), you should transfer credential via HTTP only as headers. I even think that much better would be to do that using standard Apache HTTPClient CredentialProvider (or BasicAuthorizationInterceptor from Spring Framework 4.3.1) approach to send request with the Basic Authentication header. Really to achieve security via network with Base64 encoding for credentials.

On the receiver side your should do something similar what is in the https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java#L224 :

byte[] base64Token = header.substring(6).getBytes("UTF-8");
    byte[] decoded;
    try {
        decoded = Base64.decode(base64Token);
    }
    catch (IllegalArgumentException e) {
        throw new BadCredentialsException(
                "Failed to decode basic authentication token");
    }

    String token = new String(decoded, getCredentialsCharset(request));

    int delim = token.indexOf(":");

    if (delim == -1) {
        throw new BadCredentialsException("Invalid basic authentication token");
    }
return new String[] { token.substring(0, delim), token.substring(delim + 1) };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM