Spring Integration:如何使SecurityContext传播工作?

时间:2016-07-19 10:46:30

标签: spring spring-mvc spring-security spring-integration

我试图将两个简单的Spring Web应用程序与Spring Integration集成,我想在两个应用程序之间传播Spring SecurityContext,但我还没有找到可行的解决方案。

两个应用程序彼此非常相似,配置中只有一些差异:其中一个必须是"来电者",另一个必须是"接收机"

为了获得预期的结果,我使用的是Spring Security,并且我已使用以下Spring Security配置配置了这两个应用程序:

<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>

这是有效的:如果我转到网址http://localhost:8080/caller/login,登录过程将由Spring Security层正确管理。

所以我试图配置Spring Integration模块以便集成&#34;这两个申请;我的想法(错误?)是使用来自&#34;来电者&#34;的http:outbound-gateway来调用&#34;接收者&#34;在特定的URL。 另一方面,有http:inbound-gateway来管理请求。

以下是&#34;来电者的配置&#34;侧:

<?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>

以下是&#34;接收器&#34;的配置。相反:

<?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>

正如您所看到的,我正在使用Spring Integration的组件SecurityContextPropagationChannelInterceptor,它旨在实现&#34;传播&#34;安全上下文。

我也在配置中使用int-security:secured-channels,如文档中所示。

但是这个配置不起作用:当我调用URL http://localhost:8080/caller/service/hello(记录或未记录的是相同的)时,我得到以下例外:

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

此外,类org.springframework.integration.channel.interceptor.ThreadStatePropagationChannelInterceptor具有以下方法:

@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;
}

消息实例包含已记录的Principal。 调试此方法时,我注意到已记录的Principal(例如&#34; admin&#34;)仅在&#34;调用者&#34;上获得。一方,不在&#34;接收器&#34; side(总是有#34; anonymousUser&#34;):为什么SecurityContext的传播不起作用?

我相信我的配置中出现了完全错误或遗漏的内容......

1 个答案:

答案 0 :(得分:2)

它不能通过其前提传播到另一个进程(应用程序)。 请参阅SecurityContextPropagationChannelInterceptor的实现。它基于ThreadStatePropagationChannelInterceptor。再一次线程状态,而不是通过网络到另一个进程。

由于进程间SecurityContext转移没有一个通用解决方案,因此没有任何开箱即用的方法。

当我在另一个问题(Spring Integration: the SecurityContext propagation)中回答您时,您应该仅通过HTTP将credential作为标题传输。我甚至认为使用标准的Apache HTTPClient CredentialProvider(或Spring Framework 4.3.1中的BasicAuthorizationInterceptor)方法使用Basic Authentication标头发送请求会更好。真正通过网络使用Base64编码实现凭证的安全性。

在接收方,你应该做类似于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) };