我试图将两个简单的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的传播不起作用?
我相信我的配置中出现了完全错误或遗漏的内容......
答案 0 :(得分:2)
它不能通过其前提传播到另一个进程(应用程序)。
请参阅SecurityContextPropagationChannelInterceptor
的实现。它基于ThreadStatePropagationChannelInterceptor
。再一次线程状态,而不是通过网络到另一个进程。
由于进程间SecurityContext
转移没有一个通用解决方案,因此没有任何开箱即用的方法。
当我在另一个问题(Spring Integration: the SecurityContext propagation)中回答您时,您应该仅通过HTTP将credential
作为标题传输。我甚至认为使用标准的Apache HTTPClient
CredentialProvider
(或Spring Framework 4.3.1中的BasicAuthorizationInterceptor
)方法使用Basic Authentication
标头发送请求会更好。真正通过网络使用Base64编码实现凭证的安全性。
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) };