【问题标题】:Spring Integration: how to make the SecurityContext propagation working?Spring Integration:如何使 SecurityContext 传播工作?
【发布时间】:2016-07-19 10:46:30
【问题描述】:

我正在尝试将两个简单的 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>

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

所以我正在尝试配置 Spring Integration 模块以“集成”两个应用程序;我的想法(错误?)是使用来自“调用者”的http:outbound-gateway,它在特定的 URL 调用“接收者”。 另一方面,有一个http:inbound-gateway 管理请求。

这里是“调用者”端的配置:

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

这里是“接收器”端的配置:

<?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,它旨在进行安全上下文的“传播”。

我也在配置中使用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;
}

消息实例包含记录的主体。 调试这个方法,我注意到记录的主体(例如“admin”)只在“调用者”端获得,而不是在“接收者”端(总是有“anonymousUser”):为什么传播SecurityContext 不起作用?

我认为我的配置完全错误或缺少某些东西...

【问题讨论】:

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


    【解决方案1】:

    它不能通过它的前提传播到另一个进程(应用程序)。 参见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) };
    

    【讨论】:

    • 感谢您详细的回答,Artem,现在安全传播的循环对我来说更清楚了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 2019-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-21
    相关资源
    最近更新 更多