Spring security/Spring session/Web 个插座
Spring security/Spring session/Web sockets
我们正在尝试结合使用 spring session、spring 安全和 websockets 来实现不使用 cookie 的 websocket API 的安全性。
理想情况下,我们将使用授权 header 或使用 websocket/stomp 消息的身份验证,但这似乎无法使用当前的 spring websocket 支持。
我们正在使用 pre-auth 提供程序来验证查询参数令牌并让用户登录。我可以看到正确的用户在 pre-auth 中被拉出进行握手,但 SecurityContext对连接到 websocket 的拦截器不可用。
我们的 spring 安全配置是
<!-- API security -->
<security:http use-expressions="false" realm="api" authentication-manager-ref="apiAuthenticationManager" entry-point-ref="accessDeniedAuthEntryPoint" pattern="/api/**" create-session="never">
<security:custom-filter position="FIRST" ref="sessionRepositoryFilter" />
<security:custom-filter position="PRE_AUTH_FILTER" ref="headerTokenAuthFilter" />
<security:intercept-url pattern="/api/**" access="ROLE_USER" />
<security:access-denied-handler ref="accessDeniedHandler" />
</security:http>
<security:authentication-manager id="apiAuthenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
<bean id="headerTokenAuthFilter" class="com.example.server.security.HeaderTokenAuthFilter" >
<property name="authenticationManager" ref="apiAuthenticationManager"/>
<property name="continueFilterChainOnUnsuccessfulAuthentication" value="false"/>
<property name="checkForPrincipalChanges" value="true"/>
<property name="sessionRepository" ref="sessionRepository" />
</bean>
<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl" />
<bean id="accessDeniedAuthEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
<bean id="sessionRepository" class="org.springframework.session.data.redis.RedisOperationsSessionRepository">
<constructor-arg ref="jedisConnectionFactory"/>
</bean>
<bean id="sessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter">
<constructor-arg ref="sessionRepository"/>
</bean>
我们的 websocket 配置是
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebsocketConfiguration extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
@Inject
private AuthenticationValidationInterceptor authenticationValidationInterceptor;
@Inject
private SelectorQuotingInterceptor selectorQuotingInterceptor;
@Inject
private SelectorValidationInterceptor selectorValidationInterceptor;
@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp")
.withSockJS().setSessionCookieNeeded(false);
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic")
.setRelayHost("localhost")
.setRelayPort(7672);
registry.setApplicationDestinationPrefixes("/api/data/streaming");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(
authenticationValidationInterceptor,
selectorValidationInterceptor,
selectorQuotingInterceptor);
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
}
这里是被诅咒者的同事。我们的配置基本上是正确的,但我们的问题源于对安全上下文及其从 websocket 方面的可用性的一些误解。
从 https://jira.spring.io/browse/SEC-2179 的各个子问题收集的评论引导我们从拦截器中的消息中获取登录用户主体
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
Principal userPrincipal = headerAccessor.getUser();
而不是
SecurityContextHolder.getContext().getAuthentication().getName();
我们正在尝试结合使用 spring session、spring 安全和 websockets 来实现不使用 cookie 的 websocket API 的安全性。
理想情况下,我们将使用授权 header 或使用 websocket/stomp 消息的身份验证,但这似乎无法使用当前的 spring websocket 支持。
我们正在使用 pre-auth 提供程序来验证查询参数令牌并让用户登录。我可以看到正确的用户在 pre-auth 中被拉出进行握手,但 SecurityContext对连接到 websocket 的拦截器不可用。
我们的 spring 安全配置是
<!-- API security -->
<security:http use-expressions="false" realm="api" authentication-manager-ref="apiAuthenticationManager" entry-point-ref="accessDeniedAuthEntryPoint" pattern="/api/**" create-session="never">
<security:custom-filter position="FIRST" ref="sessionRepositoryFilter" />
<security:custom-filter position="PRE_AUTH_FILTER" ref="headerTokenAuthFilter" />
<security:intercept-url pattern="/api/**" access="ROLE_USER" />
<security:access-denied-handler ref="accessDeniedHandler" />
</security:http>
<security:authentication-manager id="apiAuthenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
<bean id="headerTokenAuthFilter" class="com.example.server.security.HeaderTokenAuthFilter" >
<property name="authenticationManager" ref="apiAuthenticationManager"/>
<property name="continueFilterChainOnUnsuccessfulAuthentication" value="false"/>
<property name="checkForPrincipalChanges" value="true"/>
<property name="sessionRepository" ref="sessionRepository" />
</bean>
<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl" />
<bean id="accessDeniedAuthEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
<bean id="sessionRepository" class="org.springframework.session.data.redis.RedisOperationsSessionRepository">
<constructor-arg ref="jedisConnectionFactory"/>
</bean>
<bean id="sessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter">
<constructor-arg ref="sessionRepository"/>
</bean>
我们的 websocket 配置是
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebsocketConfiguration extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
@Inject
private AuthenticationValidationInterceptor authenticationValidationInterceptor;
@Inject
private SelectorQuotingInterceptor selectorQuotingInterceptor;
@Inject
private SelectorValidationInterceptor selectorValidationInterceptor;
@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp")
.withSockJS().setSessionCookieNeeded(false);
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic")
.setRelayHost("localhost")
.setRelayPort(7672);
registry.setApplicationDestinationPrefixes("/api/data/streaming");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(
authenticationValidationInterceptor,
selectorValidationInterceptor,
selectorQuotingInterceptor);
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
}
这里是被诅咒者的同事。我们的配置基本上是正确的,但我们的问题源于对安全上下文及其从 websocket 方面的可用性的一些误解。
从 https://jira.spring.io/browse/SEC-2179 的各个子问题收集的评论引导我们从拦截器中的消息中获取登录用户主体
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
Principal userPrincipal = headerAccessor.getUser();
而不是
SecurityContextHolder.getContext().getAuthentication().getName();