总结 我想通过 STOMP 实现 websocket 通信。Authenticate 第一次(HTTP 请求)websocket 握手时的用户,并在以后使用此 Principal 授权 websocket 消息。
问题 系统在客户端第一次尝试连接到 websocket 端点时(HTTP 握手时间)对客户端进行身份验证。我的 spring 安全过滤器和身份验证提供程序完成了它的工作并正确地验证了客户端。在此之后,我可以检查客户端是否获得了角色,并且我的身份验证对象也存储在 SecurityContext 中。(此时已建立 websocket 连接并且 HTTP 协议已被丢弃。)但是从第一次 websocket 通信中,我得到 Authentication 对象是匿名的,因为 SecurityContextHolder 以某种方式被清除,因为SecurityContextChannelInterceptor清除了它。
Spring文档声称以下内容: http: //docs.spring.io/autorepo/docs/spring-security/current/reference/htmlsingle/#websocket-authentication
WebSocket 重用在建立 WebSocket 连接时在 HTTP 请求中找到的相同身份验证信息。这意味着 HttpServletRequest 上的 Principal 将被移交给 WebSockets。如果您使用 Spring Security,则 HttpServletRequest 上的 Principal 会自动被覆盖。
我非常简单的过滤器
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
Authentication authResult =
new CertAuthenticationToken(null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authResult = getAuthenticationManager().authenticate(authResult);
if (authResult.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authResult);
LOGGER.info("Client was authenticated.");
}
chain.doFilter(request, response);
} catch (AuthenticationException ae) {
LOGGER.error("Client was not authenticated. {}", ae);
SecurityContextHolder.clearContext();
onUnsuccessfulAuthentication((HttpServletRequest) request, (HttpServletResponse) response, ae);
throw ae;
}
}
我非常简单的身份验证提供程序
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication.setAuthenticated(true);
return authentication;
}
Spring boot 与 1.3.0.BUILD-SNAPSHOT 版本一起使用。一个额外的依赖项是 spring-security-messaging 和 4.0.1.RELEASE 版本,我在默认的 spring boot 依赖项旁边使用它。
任何问题的帮助表示赞赏。
应用安全:
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Bean
CertAuthenticationFilter certAuthenticationFilter() {
return new CertAuthenticationFilter(authenticationManager);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().expressionHandler(new CustomExpressionHandler())
.antMatchers("/hello", "/websocket/**").access( "isCustomAuthorized()" )
.and()
.httpBasic()
.and()
.addFilter( certAuthenticationFilter() );
}
}
认证安全:
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class AuthenticationSecurity extends
GlobalAuthenticationConfigurerAdapter {
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CertHeaderAuthenticationProvider());
auth.authenticationProvider(new CustomWebsocketAuthenticationProvider());
auth.inMemoryAuthentication();
}
}
Websocket 安全性:
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Autowired
AuthenticationManager authenticationManager;
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpTypeMatchers(SimpMessageType.CONNECT).access("isCustomAuthorized()");
}
@Override
public ChannelSecurityInterceptor inboundChannelSecurity() {
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor(
inboundMessageSecurityMetadataSource());
channelSecurityInterceptor.setAccessDecisionManager(setupDecisionManager());
return channelSecurityInterceptor;
}
private AffirmativeBased setupDecisionManager() {
MessageExpressionVoter messageExpressionVoter = new MessageExpressionVoter<Object>();
messageExpressionVoter.setExpressionHandler(new CustomMessageSecurityExpressionHandler());
List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>();
voters.add(messageExpressionVoter);
AffirmativeBased manager = new AffirmativeBased(voters);
return manager;
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}