| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 | 
							- [[websocket]]
 
- = WebSocket Security
 
- Spring Security 4 added support for securing https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html[Spring's WebSocket support].
 
- This section describes how to use Spring Security's WebSocket support.
 
- .Direct JSR-356 Support
 
- ****
 
- Spring Security does not provide direct JSR-356 support because doing so would provide little value.
 
- This is because the format is unknown, so there is https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-intro-sub-protocol[little Spring can do to secure an unknown format].
 
- Additionally, JSR-356 does not provide a way to intercept messages, so security would be rather invasive.
 
- ****
 
- [[websocket-configuration]]
 
- == WebSocket Configuration
 
- Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
 
- To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
 
- For example:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- public class WebSocketSecurityConfig
 
-       extends AbstractSecurityWebSocketMessageBrokerConfigurer { // <1> <2>
 
-     protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
 
-         messages
 
-                 .simpDestMatchers("/user/**").authenticated() // <3>
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { // <1> <2>
 
-     override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
 
-         messages.simpDestMatchers("/user/**").authenticated() // <3>
 
-     }
 
- }
 
- ----
 
- ====
 
- This will ensure that:
 
- <1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
 
- <2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
 
- <3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
 
- Spring Security also provides xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML Namespace] support for securing WebSockets.
 
- A comparable XML based configuration looks like the following:
 
- [source,xml]
 
- ----
 
- <websocket-message-broker> <!--1--> <!--2-->
 
-     <!--3-->
 
-     <intercept-message pattern="/user/**" access="hasRole('USER')" />
 
- </websocket-message-broker>
 
- ----
 
- This will ensure that:
 
- <1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
 
- <2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
 
- <3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
 
- [[websocket-authentication]]
 
- == 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.
 
- More concretely, to ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.
 
- [[websocket-authorization]]
 
- == WebSocket Authorization
 
- Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
 
- To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
 
- For example:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
 
-     @Override
 
-     protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
 
-         messages
 
-                 .nullDestMatcher().authenticated() // <1>
 
-                 .simpSubscribeDestMatchers("/user/queue/errors").permitAll() // <2>
 
-                 .simpDestMatchers("/app/**").hasRole("USER") // <3>
 
-                 .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") // <4>
 
-                 .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() // <5>
 
-                 .anyMessage().denyAll(); // <6>
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
 
-     override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
 
-         messages
 
-             .nullDestMatcher().authenticated() // <1>
 
-             .simpSubscribeDestMatchers("/user/queue/errors").permitAll() // <2>
 
-             .simpDestMatchers("/app/**").hasRole("USER") // <3>
 
-             .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") // <4>
 
-             .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() // <5>
 
-             .anyMessage().denyAll() // <6>
 
-     }
 
- }
 
- ----
 
- ====
 
- This will ensure that:
 
- <1> Any message without a destination (i.e. anything other than Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated
 
- <2> Anyone can subscribe to /user/queue/errors
 
- <3> Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
 
- <4> Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
 
- <5> Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
 
- <6> Any other Message is rejected. This is a good idea to ensure that you do not miss any messages.
 
- Spring Security also provides xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML Namespace] support for securing WebSockets.
 
- A comparable XML based configuration looks like the following:
 
- [source,xml]
 
- ----
 
- <websocket-message-broker>
 
-     <!--1-->
 
-     <intercept-message type="CONNECT" access="permitAll" />
 
-     <intercept-message type="UNSUBSCRIBE" access="permitAll" />
 
-     <intercept-message type="DISCONNECT" access="permitAll" />
 
-     <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> <!--2-->
 
-     <intercept-message pattern="/app/**" access="hasRole('USER')" />      <!--3-->
 
-     <!--4-->
 
-     <intercept-message pattern="/user/**" access="hasRole('USER')" />
 
-     <intercept-message pattern="/topic/friends/*" access="hasRole('USER')" />
 
-     <!--5-->
 
-     <intercept-message type="MESSAGE" access="denyAll" />
 
-     <intercept-message type="SUBSCRIBE" access="denyAll" />
 
-     <intercept-message pattern="/**" access="denyAll" /> <!--6-->
 
- </websocket-message-broker>
 
- ----
 
- This will ensure that:
 
- <1> Any message of type CONNECT, UNSUBSCRIBE, or DISCONNECT will require the user to be authenticated
 
- <2> Anyone can subscribe to /user/queue/errors
 
- <3> Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
 
- <4> Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
 
- <5> Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
 
- <6> Any other message with a destination is rejected. This is a good idea to ensure that you do not miss any messages.
 
- [[websocket-authorization-notes]]
 
- === WebSocket Authorization Notes
 
- In order to properly secure your application it is important to understand Spring's WebSocket support.
 
- [[websocket-authorization-notes-messagetypes]]
 
- ==== WebSocket Authorization on Message Types
 
- It is important to understand the distinction between SUBSCRIBE and MESSAGE types of messages and how it works within Spring.
 
- Consider a chat application.
 
- * The system can send notifications MESSAGE to all users through a destination of "/topic/system/notifications"
 
- * Clients can receive notifications by SUBSCRIBE to the "/topic/system/notifications".
 
- While we want clients to be able to SUBSCRIBE to "/topic/system/notifications", we do not want to enable them to send a MESSAGE to that destination.
 
- If we allowed sending a MESSAGE to "/topic/system/notifications", then clients could send a message directly to that endpoint and impersonate the system.
 
- In general, it is common for applications to deny any MESSAGE sent to a destination that starts with the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp[broker prefix] (i.e. "/topic/" or "/queue/").
 
- [[websocket-authorization-notes-destinations]]
 
- ==== WebSocket Authorization on Destinations
 
- It is also is important to understand how destinations are transformed.
 
- Consider a chat application.
 
- * Users can send messages to a specific user by sending a message to the destination of "/app/chat".
 
- * The application sees the message, ensures that the "from" attribute is specified as the current user (we cannot trust the client).
 
- * The application then sends the message to the recipient using `SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)`.
 
- * The message gets turned into the destination of "/queue/user/messages-<sessionid>"
 
- With the application above, we want to allow our client to listen to "/user/queue" which is transformed into "/queue/user/messages-<sessionid>".
 
- However, we do not want the client to be able to listen to "/queue/*" because that would allow the client to see messages for every user.
 
- In general, it is common for applications to deny any SUBSCRIBE sent to a message that starts with the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp[broker prefix] (i.e. "/topic/" or "/queue/").
 
- Of course we may provide exceptions to account for things like
 
- [[websocket-authorization-notes-outbound]]
 
- === Outbound Messages
 
- Spring contains a section titled https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow[Flow of Messages] that describes how messages flow through the system.
 
- It is important to note that Spring Security only secures the `clientInboundChannel`.
 
- Spring Security does not attempt to secure the `clientOutboundChannel`.
 
- The most important reason for this is performance.
 
- For every message that goes in, there are typically many more that go out.
 
- Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.
 
- [[websocket-sameorigin]]
 
- == Enforcing Same Origin Policy
 
- It is important to emphasize that the browser does not enforce the https://en.wikipedia.org/wiki/Same-origin_policy[Same Origin Policy] for WebSocket connections.
 
- This is an extremely important consideration.
 
- [[websocket-sameorigin-why]]
 
- === Why Same Origin?
 
- Consider the following scenario.
 
- A user visits bank.com and authenticates to their account.
 
- The same user opens another tab in their browser and visits evil.com.
 
- The Same Origin Policy ensures that evil.com cannot read or write data to bank.com.
 
- With WebSockets the Same Origin Policy does not apply.
 
- In fact, unless bank.com explicitly forbids it, evil.com can read and write data on behalf of the user.
 
- This means that anything the user can do over the webSocket (i.e. transfer money), evil.com can do on that users behalf.
 
- Since SockJS tries to emulate WebSockets it also bypasses the Same Origin Policy.
 
- This means developers need to explicitly protect their applications from external domains when using SockJS.
 
- [[websocket-sameorigin-spring]]
 
- === Spring WebSocket Allowed Origin
 
- Fortunately, since Spring 4.1.5 Spring's WebSocket and SockJS support restricts access to the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-allowed-origins[current domain].
 
- Spring Security adds an additional layer of protection to provide https://en.wikipedia.org/wiki/Defense_in_depth_(computing)[defence in depth].
 
- [[websocket-sameorigin-csrf]]
 
- === Adding CSRF to Stomp Headers
 
- By default Spring Security requires the xref:features/exploits/csrf.adoc#csrf[CSRF token] in any CONNECT message type.
 
- This ensures that only a site that has access to the CSRF token can connect.
 
- Since only the *Same Origin* can access the CSRF token, external domains are not allowed to make a connection.
 
- Typically we need to include the CSRF token in an HTTP header or an HTTP parameter.
 
- However, SockJS does not allow for these options.
 
- Instead, we must include the token in the Stomp headers
 
- Applications can xref:servlet/exploits/csrf.adoc#servlet-csrf-include[obtain a CSRF token] by accessing the request attribute named _csrf.
 
- For example, the following will allow accessing the `CsrfToken` in a JSP:
 
- [source,javascript]
 
- ----
 
- var headerName = "${_csrf.headerName}";
 
- var token = "${_csrf.token}";
 
- ----
 
- If you are using static HTML, you can expose the `CsrfToken` on a REST endpoint.
 
- For example, the following would expose the `CsrfToken` on the URL /csrf
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @RestController
 
- public class CsrfController {
 
-     @RequestMapping("/csrf")
 
-     public CsrfToken csrf(CsrfToken token) {
 
-         return token;
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @RestController
 
- class CsrfController {
 
-     @RequestMapping("/csrf")
 
-     fun csrf(token: CsrfToken): CsrfToken {
 
-         return token
 
-     }
 
- }
 
- ----
 
- ====
 
- The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token.
 
- We can now include the token in our Stomp client.
 
- For example:
 
- [source,javascript]
 
- ----
 
- ...
 
- var headers = {};
 
- headers[headerName] = token;
 
- stompClient.connect(headers, function(frame) {
 
-   ...
 
- }
 
- ----
 
- [[websocket-sameorigin-disable]]
 
- === Disable CSRF within WebSockets
 
- If you want to allow other domains to access your site, you can disable Spring Security's protection.
 
- For example, in Java Configuration you can use the following:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
 
-     ...
 
-     @Override
 
-     protected boolean sameOriginDisabled() {
 
-         return true;
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
 
-     // ...
 
-     override fun sameOriginDisabled(): Boolean {
 
-         return true
 
-     }
 
- }
 
- ----
 
- ====
 
- [[websocket-sockjs]]
 
- == Working with SockJS
 
- https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-fallback[SockJS] provides fallback transports to support older browsers.
 
- When using the fallback options we need to relax a few security constraints to allow SockJS to work with Spring Security.
 
- [[websocket-sockjs-sameorigin]]
 
- === SockJS & frame-options
 
- SockJS may use an https://github.com/sockjs/sockjs-client/tree/v0.3.4[transport that leverages an iframe].
 
- By default Spring Security will xref:features/exploits/headers.adoc#headers-frame-options[deny] the site from being framed to prevent Clickjacking attacks.
 
- To allow SockJS frame based transports to work, we need to configure Spring Security to allow the same origin to frame the content.
 
- You can customize X-Frame-Options with the xref:servlet/appendix/namespace/http.adoc#nsa-frame-options[frame-options] element.
 
- For example, the following will instruct Spring Security to use "X-Frame-Options: SAMEORIGIN" which allows iframes within the same domain:
 
- [source,xml]
 
- ----
 
- <http>
 
-     <!-- ... -->
 
-     <headers>
 
-         <frame-options
 
-           policy="SAMEORIGIN" />
 
-     </headers>
 
- </http>
 
- ----
 
- Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class WebSecurityConfig extends
 
-    WebSecurityConfigurerAdapter {
 
-     @Override
 
-     protected void configure(HttpSecurity http) throws Exception {
 
-         http
 
-             // ...
 
-             .headers(headers -> headers
 
-                 .frameOptions(frameOptions -> frameOptions
 
-                      .sameOrigin()
 
-                 )
 
-         );
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         http {
 
-             // ...
 
-             headers {
 
-                 frameOptions {
 
-                     sameOrigin = true
 
-                 }
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- [[websocket-sockjs-csrf]]
 
- === SockJS & Relaxing CSRF
 
- SockJS uses a POST on the CONNECT messages for any HTTP based transport.
 
- Typically we need to include the CSRF token in an HTTP header or an HTTP parameter.
 
- However, SockJS does not allow for these options.
 
- Instead, we must include the token in the Stomp headers as described in <<websocket-sameorigin-csrf>>.
 
- It also means we need to relax our CSRF protection with the web layer.
 
- Specifically, we want to disable CSRF protection for our connect URLs.
 
- We do NOT want to disable CSRF protection for every URL.
 
- Otherwise our site will be vulnerable to CSRF attacks.
 
- We can easily achieve this by providing a CSRF RequestMatcher.
 
- Our Java Configuration makes this extremely easy.
 
- For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- @EnableWebSecurity
 
- public class WebSecurityConfig
 
-     extends WebSecurityConfigurerAdapter {
 
-     @Override
 
-     protected void configure(HttpSecurity http) throws Exception {
 
-         http
 
-             .csrf(csrf -> csrf
 
-                 // ignore our stomp endpoints since they are protected using Stomp headers
 
-                 .ignoringAntMatchers("/chat/**")
 
-             )
 
-             .headers(headers -> headers
 
-                 // allow same origin to frame our site to support iframe SockJS
 
-                 .frameOptions(frameOptions -> frameOptions
 
-                     .sameOrigin()
 
-                 )
 
-             )
 
-             .authorizeHttpRequests(authorize -> authorize
 
-                 ...
 
-             )
 
-             ...
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- @EnableWebSecurity
 
- open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         http {
 
-             csrf {
 
-                 ignoringAntMatchers("/chat/**")
 
-             }
 
-             headers {
 
-                 frameOptions {
 
-                     sameOrigin = true
 
-                 }
 
-             }
 
-             authorizeRequests {
 
-                 // ...
 
-             }
 
-             // ...
 
- ----
 
- ====
 
- If we are using XML based configuration, we can use the xref:servlet/appendix/namespace/http.adoc#nsa-csrf-request-matcher-ref[csrf@request-matcher-ref].
 
- For example:
 
- [source,xml]
 
- ----
 
- <http ...>
 
-     <csrf request-matcher-ref="csrfMatcher"/>
 
-     <headers>
 
-         <frame-options policy="SAMEORIGIN"/>
 
-     </headers>
 
-     ...
 
- </http>
 
- <b:bean id="csrfMatcher"
 
-     class="AndRequestMatcher">
 
-     <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
 
-     <b:constructor-arg>
 
-         <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
 
-           <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
 
-             <b:constructor-arg value="/chat/**"/>
 
-           </b:bean>
 
-         </b:bean>
 
-     </b:constructor-arg>
 
- </b:bean>
 
- ----
 
 
  |