123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707 |
- [[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, and 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 invasive.
- ****
- [[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.
- In Spring Security 5.8, this support has been refreshed to use the `AuthorizationManager` API.
- To configure authorization using Java Configuration, simply include the `@EnableWebSocketSecurity` annotation and publish an `AuthorizationManager<Message<?>>` bean or in xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML] use the `use-authorization-manager` attribute.
- One way to do this is by using the `AuthorizationManagerMessageMatcherRegistry` to specify endpoint patterns like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSocketSecurity // <1> <2>
- public class WebSocketSecurityConfig {
- @Bean
- AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
- messages
- .simpDestMatchers("/user/**").hasRole("USER") // <3>
- return messages.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSocketSecurity // <1> <2>
- open class WebSocketSecurityConfig { // <1> <2>
- @Bean
- fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
- messages.simpDestMatchers("/user/**").hasRole("USER") // <3>
- return messages.build()
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <websocket-message-broker use-authorization-manager="true"> <1> <2>
- <intercept-message pattern="/user/**" access="hasRole('USER')"/> <3>
- </websocket-message-broker>
- ----
- ======
- <1> Any inbound CONNECT message requires a valid CSRF token to enforce the <<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`. You can find additional details on authorization in <<websocket-authorization>>
- === Custom Authorization
- When using `AuthorizationManager`, customization is quite simple.
- For example, you can publish an `AuthorizationManager` that requires that all messages have a role of "USER" using `AuthorityAuthorizationManager`, as seen below:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSocketSecurity // <1> <2>
- public class WebSocketSecurityConfig {
- @Bean
- AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
- return AuthorityAuthorizationManager.hasRole("USER");
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSocketSecurity // <1> <2>
- open class WebSocketSecurityConfig {
- @Bean
- fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
- return AuthorityAuthorizationManager.hasRole("USER") // <3>
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <bean id="authorizationManager" class="org.example.MyAuthorizationManager"/>
- <websocket-message-broker authorization-manager-ref="myAuthorizationManager"/>
- ----
- ======
- There are several ways to further match messages, as can be seen in a more advanced example below:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- public class WebSocketSecurityConfig {
- @Bean
- public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder 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>
- return messages.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- open class WebSocketSecurityConfig {
- fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
- 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>
- return messages.build();
- }
- }
- ----
- Xml::
- +
- [source,kotlin,role="secondary"]
- ----
- <websocket-message-broker use-authorization-manager="true">
- <!--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/**" type="SUBSCRIBE" access="hasRole('USER')" />
- <intercept-message pattern="/topic/friends/*" type="SUBSCRIBE" 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 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.
- [[migrating-spel-expressions]]
- === Migrating SpEL Expressions
- If you are migrating from an older version of Spring Security, your destination matchers may include SpEL expressions.
- It's recommended that these be changed to using concrete implementations of `AuthorizationManager` since this is independently testable.
- However, to ease migration, you can also use a class like the following:
- [source,java]
- ----
- public final class MessageExpressionAuthorizationManager implements AuthorizationManager<MessageAuthorizationContext<?>> {
- private SecurityExpressionHandler<Message<?>> expressionHandler = new DefaultMessageSecurityExpressionHandler();
- private Expression expression;
- public MessageExpressionAuthorizationManager(String expressionString) {
- Assert.hasText(expressionString, "expressionString cannot be empty");
- this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
- }
- @Override
- public AuthorizationDecision check(Supplier<Authentication> authentication, MessageAuthorizationContext<?> context) {
- EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context.getMessage());
- boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
- return new ExpressionAuthorizationDecision(granted, this.expression);
- }
- }
- ----
- And specify an instance for each matcher that you cannot get migrate:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- public class WebSocketSecurityConfig {
- @Bean
- public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
- messages
- // ...
- .simpSubscribeDestMatchers("/topic/friends/{friend}").access(new MessageExpressionAuthorizationManager("#friends == 'john"));
- // ...
- return messages.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- open class WebSocketSecurityConfig {
- fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?> {
- messages
- // ..
- .simpSubscribeDestMatchers("/topic/friends/{friends}").access(MessageExpressionAuthorizationManager("#friends == 'john"))
- // ...
- return messages.build()
- }
- }
- ----
- ======
- [[websocket-authorization-notes]]
- === WebSocket Authorization Notes
- To properly secure your application, you need to understand Spring's WebSocket support.
- [[websocket-authorization-notes-messagetypes]]
- ==== WebSocket Authorization on Message Types
- You need to understand the distinction between `SUBSCRIBE` and `MESSAGE` types of messages and how they work within Spring.
- Consider a chat application:
- * The system can send a notification `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`, 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] (`/topic/` or `/queue/`).
- [[websocket-authorization-notes-destinations]]
- ==== WebSocket Authorization on Destinations
- You should also understand how destinations are transformed.
- Consider a chat application:
- * Users can send messages to a specific user by sending a message to the `/app/chat` destination.
- * 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 by using `SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)`.
- * The message gets turned into the destination of `/queue/user/messages-<sessionid>`.
- With this chat application, we want to let our client to listen `/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 let the client 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] (`/topic/` or `/queue/`).
- We may provide exceptions to account for things like
- //FIXME: Like what?
- [[websocket-authorization-notes-outbound]]
- === Outbound Messages
- The Spring Framework reference documentation 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.
- Note that Spring Security secures only 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, typically many more go out.
- Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.
- [[websocket-sameorigin]]
- == Enforcing Same Origin Policy
- Note 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 data from 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 (such as transferring money), `evil.com` can do on that user's behalf.
- Since SockJS tries to emulate WebSockets, it also bypasses the Same Origin Policy.
- This means that developers need to explicitly protect their applications from external domains when they use 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/Defence_in_depth_(non-military)#Information_security[defense 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#csrf-integration[obtain a CSRF token] by accessing the request attribute named `_csrf`.
- For example, the following allows accessing the `CsrfToken` in a JSP:
- [source,javascript]
- ----
- var headerName = "${_csrf.headerName}";
- var token = "${_csrf.token}";
- ----
- If you use static HTML, you can expose the `CsrfToken` on a REST endpoint.
- For example, the following would expose the `CsrfToken` on the `/csrf` URL:
- [tabs]
- ======
- 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:
- [source,javascript]
- ----
- ...
- var headers = {};
- headers[headerName] = token;
- stompClient.connect(headers, function(frame) {
- ...
- })
- ----
- [[websocket-sameorigin-disable]]
- === Disable CSRF within WebSockets
- NOTE: At this point, CSRF is not configurable when using `@EnableWebSocketSecurity`, though this will likely be added in a future release.
- To disable CSRF, instead of using `@EnableWebSocketSecurity`, you can use XML support or add the Spring Security components yourself, like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
- private final ApplicationContext applicationContext;
- private final AuthorizationManager<Message<?>> authorizationManager;
- public WebSocketSecurityConfig(ApplicationContext applicationContext, AuthorizationManager<Message<?>> authorizationManager) {
- this.applicationContext = applicationContext;
- this.authorizationManager = authorizationManager;
- }
- @Override
- public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
- argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
- }
- @Override
- public void configureClientInboundChannel(ChannelRegistration registration) {
- AuthorizationChannelInterceptor authz = new AuthorizationChannelInterceptor(authorizationManager);
- AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(applicationContext);
- authz.setAuthorizationEventPublisher(publisher);
- registration.interceptors(new SecurityContextChannelInterceptor(), authz);
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- open class WebSocketSecurityConfig(val applicationContext: ApplicationContext, val authorizationManager: AuthorizationManager<Message<*>>) : WebSocketMessageBrokerConfigurer {
- @Override
- override fun addArgumentResolvers(argumentResolvers: List<HandlerMethodArgumentResolver>) {
- argumentResolvers.add(AuthenticationPrincipalArgumentResolver())
- }
- @Override
- override fun configureClientInboundChannel(registration: ChannelRegistration) {
- var authz: AuthorizationChannelInterceptor = AuthorizationChannelInterceptor(authorizationManager)
- var publisher: AuthorizationEventPublisher = SpringAuthorizationEventPublisher(applicationContext)
- authz.setAuthorizationEventPublisher(publisher)
- registration.interceptors(SecurityContextChannelInterceptor(), authz)
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <websocket-message-broker use-authorization-manager="true" same-origin-disabled="true">
- <intercept-message pattern="/**" access="authenticated"/>
- </websocket-message-broker>
- ----
- ======
- [[websocket-expression-handler]]
- === Custom Expression Handler
- At times, there may be value in customizing how the `access` expressions are handled defined in your `intercept-message` XML elements.
- To do this, you can create a class of type `SecurityExpressionHandler<MessageAuthorizationContext<?>>` and refer to it in your XML definition like so:
- [source,xml]
- ----
- <websocket-message-broker use-authorization-manager="true">
- <expression-handler ref="myRef"/>
- ...
- </websocket-message-broker>
- <b:bean ref="myRef" class="org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler"/>
- ----
- If you are migrating from a legacy usage of `websocket-message-broker` that implements a `SecurityExpressionHandler<Message<?>>`, you can:
- 1. Additionally implement the `createEvaluationContext(Supplier, Message)` method and then
- 2. Wrap that value in a `MessageAuthorizationContextSecurityExpressionHandler` like so:
- [source,xml]
- ----
- <websocket-message-broker use-authorization-manager="true">
- <expression-handler ref="myRef"/>
- ...
- </websocket-message-broker>
- <b:bean ref="myRef" class="org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler">
- <b:constructor-arg>
- <b:bean class="org.example.MyLegacyExpressionHandler"/>
- </b:constructor-arg>
- </b:bean>
- ----
- [[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 a https://github.com/sockjs/sockjs-client/tree/v0.3.4[transport that leverages an iframe].
- By default, Spring Security xref:features/exploits/headers.adoc#headers-frame-options[denies] the site from being framed to prevent clickjacking attacks.
- To allow SockJS frame-based transports to work, we need to configure Spring Security to let the same origin 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 instructs 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 by using the following:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class WebSecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http
- // ...
- .headers((headers) -> headers
- .frameOptions((frameOptions) -> frameOptions
- .sameOrigin()
- )
- );
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSecurity
- open class WebSecurityConfig {
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- http {
- // ...
- headers {
- frameOptions {
- sameOrigin = true
- }
- }
- }
- return http.build()
- }
- }
- ----
- ======
- [[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 that 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 is vulnerable to CSRF attacks.
- We can easily achieve this by providing a CSRF `RequestMatcher`.
- Our Java configuration makes this easy.
- For example, if our stomp endpoint is `/chat`, we can disable CSRF protection only for URLs that start with `/chat/` by using the following configuration:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class WebSecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http
- .csrf((csrf) -> csrf
- // ignore our stomp endpoints since they are protected using Stomp headers
- .ignoringRequestMatchers("/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 {
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- http {
- csrf {
- ignoringRequestMatchers("/chat/**")
- }
- headers {
- frameOptions {
- sameOrigin = true
- }
- }
- authorizeRequests {
- // ...
- }
- // ...
- }
- }
- }
- ----
- ======
- If we use XML-based configuration, we can use thexref:servlet/appendix/namespace/http.adoc#nsa-csrf-request-matcher-ref[csrf@request-matcher-ref].
- [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.config.http.PathPatternRequestMatcherFactoryBean">
- <b:constructor-arg value="/chat/**"/>
- </b:bean>
- </b:bean>
- </b:constructor-arg>
- </b:bean>
- ----
|