I'm using Spring Boot (v1.5.10.RELEASE) to create a backend for an application written in Angular. The back is secured using spring security + keycloak. Now I'm adding a websocket, using STOMP over SockJS, and wanted to secure it. I'm trying to follow the docs at Websocket Token Authentication, and it shows the following piece of code:
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ... ; // access authentication header(s)
accessor.setUser(user);
}
I'm able to retrieve the bearer token from the client using:
String token = accessor.getNativeHeader("Authorization").get(0);
My question is, how can I convert that to an Authentication object? Or how to proceed from here? Because I always get 403. This is my websocket security config:
@Configuration
public class WebSocketSecurityConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry
messages) {
messages.simpDestMatchers("/app/**").authenticated().simpSubscribeDestMatchers("/topic/**").authenticated()
.anyMessage().denyAll();
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
And this is the Web security configuration:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class WebSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authenticationProvider(keycloakAuthenticationProvider())
.addFilterBefore(keycloakAuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.authorizeRequests()
.requestMatchers(new NegatedRequestMatcher(new AntPathRequestMatcher("/management/**")))
.hasRole("USER");
}
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Any help or ideas are welcome.
I like the answer of adrianmoya except for the part of the KeycloakTokenVerifier. I use the following instead:
I was able to do websocket authentication/authorization without using Spring Security and SockJS:
Handshake interceptor:
Websocket controller:
Client side (javascript):
build.gradle:
As you can see the client is authenticated during the handshake. The
HandshakeInterceptor
class extracts the token from theSec-WebSocket-Protocol
header. No SockJS or Spring Security is needed. Hope this helps :)I was able to enable token based authentication, following the recomendations by Raman on this question. Here's the final code to make it work:
1) First, create a class that represent the JWS auth token:
2) Then, create an authenticator that handles the JWSToken, validating against keycloak. User is my own app class that represents a user:
3) The class that actually validates the token against keycloak by calling the certs endpoint to validate the token signature, based on this gists. It returns a keycloak AccessToken:
4) Finally, inject the authenticator in the Websocket configuration and complete the piece of code as recommended by spring docs:
I also changed my security configuration a bit. First, I excluded the WS endpoint from spring web securty, and also let the connection methods open to anyone in the websocket security:
In WebSecurityConfiguration:
And in the class WebSocketSecurityConfig:
So the final result is: anybody in the local network can connect to the socket, but to actually subscribe to any channel, you have to be authenticated, so you need to send the Bearer token with the original CONNECT message or you'll get UnauthorizedException. Hope it helps others with this requeriment!