Summary I would like to implement websocket communication over STOMP. Authenticate The user in the time of the first (HTTP request) websocket handshake and use this Principal for authorizing websocket messages later.
Problem The system authenticates the client at the first time when it tries to connect to the websocket endpoint (the time of HTTP handshake). My spring security filter and Authentication provider does its job and authenticates the client properly. After this I can check that the client gets the Roles and my Authentication object is stored in the SecurityContext as well. (At this point websocket connection established and the HTTP protocol has been thrown away.) BUT from the first websocket communication I get that the Authentication object is Anonymous because the SecurityContextHolder was cleared somehow because the SecurityContextChannelInterceptor clears it.
Spring documentation claims the following: http://docs.spring.io/autorepo/docs/spring-security/current/reference/htmlsingle/#websocket-authentication
WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made. This means that the Principal on the HttpServletRequest will be handed off to WebSockets. If you are using Spring Security, the Principal on the HttpServletRequest is overridden automatically.
My very simple filter
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;
}
}
My very simple authentication provider
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication.setAuthenticated(true);
return authentication;
}
Spring boot is used with 1.3.0.BUILD-SNAPSHOT version. One additonal dependency is spring-security-messaging with 4.0.1.RELEASE version that I use beside the default spring boot dependencies.
Any question help is appreciated.
Application Security:
@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() );
}
}
Authentication Security:
@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 security:
@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;
}
}