Summary I would like to implement network communication through STOMP. Authenticating the user during the first handshake of the web layout (HTTP request) and using this Principal to authorize messages on the Internet.
Problem The system authenticates the client for the first time when it tries to connect to the websocket endpoint (HTTP connection establishment time). My spring security filter and authentication provider do their job and authenticate the client. After that, I can verify that the client is receiving Roles, and my authentication object is also stored in the SecurityContext. (At this point, the websocket connection was established and the HTTP protocol was deleted). BUT from the first connection with websocket. I get the Authentication object announced because the SecurityContextHolder was somehow cleared, because the SecurityContextChannelInterceptor removes it.
Spring documentation states the following: http://docs.spring.io/autorepo/docs/spring-security/current/reference/htmlsingle/#websocket-authentication
WebSockets reuse the same authentication information that is contained in the HTTP request when the WebSocket connection was established. This means that the HttpServletRequest Principal will be passed to WebSockets. If you use spring Security, the HttpServletRequest Principal is automatically overridden.
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; } }
The easiest authentication provider
public Authentication authenticate(Authentication authentication) throws AuthenticationException { authentication.setAuthenticated(true); return authentication; }
Spring boot is used with version 1.3.0.BUILD-SNAPSHOT. One additional dependency is spring-security-messaging with version 4.0.1.RELEASE, which I use in addition to the standard spring boot dependencies.
Any help regarding questions 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; } }