Spring Websockets Authentication with Spring Security and Keycloak

后端 未结 3 984
半阙折子戏
半阙折子戏 2021-01-02 07:03

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 w

3条回答
  •  情话喂你
    2021-01-02 07:47

    I was able to do websocket authentication/authorization without using Spring Security and SockJS:

    @Configuration
    @EnableWebSocketMessageBroker
    @RequiredArgsConstructor
    public class StompConfiguration implements WebSocketMessageBrokerConfigurer {
    
        private final KeycloakSpringBootProperties configuration;
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.setApplicationDestinationPrefixes("/stompy");  // prefix for incoming messages in @MessageMapping
            config.enableSimpleBroker("/broker");                 // enabling broker @SendTo("/broker/blabla")
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/stomp")
                    .addInterceptors(new StompHandshakeInterceptor(configuration))
                    .setAllowedOrigins("*");
        }
    }
    

    Handshake interceptor:

    @Slf4j
    @RequiredArgsConstructor
    public class StompHandshakeInterceptor implements HandshakeInterceptor {
    
        private final KeycloakSpringBootProperties configuration;
    
        @Override
        public boolean beforeHandshake(ServerHttpRequest req, ServerHttpResponse resp, WebSocketHandler h, Map atts) {
            List protocols = req.getHeaders().get("Sec-WebSocket-Protocol");
            try {
                String token = protocols.get(0).split(", ")[2];
                log.debug("Token: " + token);
                AdapterTokenVerifier.verifyToken(token, KeycloakDeploymentBuilder.build(configuration));
                resp.setStatusCode(HttpStatus.SWITCHING_PROTOCOLS);
                log.debug("token valid");
            } catch (IndexOutOfBoundsException e) {
                resp.setStatusCode(HttpStatus.UNAUTHORIZED);
                return false;
            }
            catch (VerificationException e) {
                resp.setStatusCode(HttpStatus.FORBIDDEN);
                log.error(e.getMessage());
                return false;
            }
            return true;
        }
    
        @Override
        public void afterHandshake(ServerHttpRequest rq, ServerHttpResponse rp, WebSocketHandler h, @Nullable Exception e) {}
    }
    

    Websocket controller:

    @Controller
    public class StompController {
        @MessageMapping("/test")
        @SendTo("/broker/lol")
        public String lol(String message) {
            System.out.println("Incoming message: " + message);
            return message;
        }
    }
    

    Client side (javascript):

    function connect() {
        let protocols = ['v10.stomp', 'v11.stomp'];
        protocols.push("KEYCLOAK TOKEN");
        const url = "ws://localhost:8080/stomp";
    
        client = Stomp.client(url, protocols);
        client.connect(
            {},
            () => {
                console.log("Connection established");
                client.subscribe("/broker/lol", function (mes) {
                    console.log("New message for /broker/lol: " + mes.body);
                });
            },
            error => { console.log("ERROR: " + error); }
        );
    }
    
    function sendMessage() {
        let message = "test message";
        if (client) client.send("/stompy/test", {}, message);
    }
    

    build.gradle:

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-starter-websocket'
        compileOnly 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    
        // keycloak
        implementation 'org.keycloak:keycloak-spring-boot-starter'
    
        // stomp.js
        implementation("org.webjars:webjars-locator-core")
        implementation("org.webjars:stomp-websocket:2.3.3")
    }
    
    dependencyManagement {
        imports {
            mavenBom "org.keycloak.bom:keycloak-adapter-bom:$keycloakVersion"
        }
    }
    

    As you can see the client is authenticated during the handshake. The HandshakeInterceptor class extracts the token from the Sec-WebSocket-Protocol header. No SockJS or Spring Security is needed. Hope this helps :)

提交回复
热议问题