Перейти к основному содержимому

Введение в безопасность и веб-сокеты

· 6 мин. чтения

1. Введение

В предыдущей статье мы показали, как добавить WebSockets в проект Spring MVC.

Здесь мы опишем, как добавить безопасность к Spring WebSockets в Spring MVC . Прежде чем продолжить, убедитесь, что у вас уже есть базовое покрытие безопасности Spring MVC — если нет, ознакомьтесь с этой статьей .

2. Зависимости Maven

Есть две основные группы зависимостей Maven , которые нам нужны для нашей реализации WebSocket.

Во-первых, давайте укажем общие версии Spring Framework и Spring Security, которые мы будем использовать:

<properties>
<spring.version>5.3.13</spring.version>
<spring-security.version>5.6.0</spring-security.version>
</properties>

Во-вторых, давайте добавим основные библиотеки Spring MVC и Spring Security, необходимые для реализации базовой аутентификации и авторизации:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>

Последние версии spring-core , spring-web , spring-webmvc , spring-security-web , spring-security-config можно найти на Maven Central.

Наконец, давайте добавим необходимые зависимости:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
<version>${spring-security.version}</version>
</dependency>

Вы можете найти последнюю версию spring-websocket , spring-messaging и spring-security-messaging на Maven Central.

3. Базовая безопасность веб-сокетов

Безопасность, специфичная для WebSocket, с использованием библиотеки spring-security-messaging сосредоточена на классе AbstractSecurityWebSocketMessageBrokerConfigurer и его реализации в вашем проекте:

@Configuration
public class SocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
//...
}

Класс AbstractSecurityWebSocketMessageBrokerConfigurer обеспечивает дополнительную защиту, обеспечиваемую WebSecurityConfigurerAdapter .

Библиотека spring-security-messaging — не единственный способ реализовать безопасность для WebSockets. Если мы будем придерживаться обычной библиотеки spring-websocket , мы сможем реализовать интерфейс WebSocketConfigurer и подключить перехватчики безопасности к нашим обработчикам сокетов.

Поскольку мы используем библиотеку обмена сообщениями spring-security , мы будем использовать подход AbstractSecurityWebSocketMessageBrokerConfigurer .

3.1. Реализация configureInbound()

Реализация configureInbound() является наиболее важным шагом в настройке вашего подкласса AbstractSecurityWebSocketMessageBrokerConfigurer :

@Override 
protected void configureInbound(
MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**").authenticated()
.anyMessage().authenticated();
}

В то время как WebSecurityConfigurerAdapter позволяет указывать различные требования авторизации для разных маршрутов в рамках всего приложения, AbstractSecurityWebSocketMessageBrokerConfigurer позволяет указывать конкретные требования авторизации для назначений сокетов.

3.2. Соответствие типа и назначения

MessageSecurityMetadataSourceRegistry позволяет нам указать ограничения безопасности, такие как пути, роли пользователей и разрешенные сообщения.

Сопоставители типов ограничивают, какие SimpMessageType разрешены и каким образом :

.simpTypeMatchers(CONNECT, UNSUBSCRIBE, DISCONNECT).permitAll()

Сопоставители назначения ограничивают, какие шаблоны конечных точек доступны и каким образом :

.simpDestMatchers("/app/**").hasRole("ADMIN")

Сопоставители адресатов подписки сопоставляют список экземпляров SimpDestinationMessageMatcher , которые соответствуют SimpMessageType.SUBSCRIBE : ``

.simpSubscribeDestMatchers("/topic/**").authenticated()

Вот полный список всех доступных методов для сопоставления типов и назначений .

4. Защита маршрутов сокетов

Теперь, когда мы познакомились с базовой безопасностью сокетов и конфигурацией сопоставления типов, мы можем комбинировать безопасность сокетов, представления, STOMP (протокол обмена текстовыми сообщениями), брокеры сообщений и контроллеры сокетов, чтобы включить безопасные веб-сокеты в нашем приложении Spring MVC.

Во-первых, давайте настроим наши представления сокетов и контроллеры для базового охвата Spring Security:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.foreach.springsecuredsockets")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/index", "/authenticate").permitAll()
.antMatchers(
"/secured/**/**",
"/secured/success",
"/secured/socket",
"/secured/success").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/authenticate")
//...
}
}

Во-вторых, давайте настроим фактическое назначение сообщения с требованиями аутентификации:

@Configuration
public class SocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**").authenticated()
.anyMessage().authenticated();
}
}

Теперь в нашем WebSocketMessageBrokerConfigurer мы можем зарегистрировать фактическое сообщение и конечные точки STOMP:

@Configuration
@EnableWebSocketMessageBroker
public class SocketBrokerConfig
implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/secured/history");
config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/secured/chat")
.withSockJS();
}
}

Давайте определим пример контроллера сокета и конечной точки, для которых мы обеспечили безопасность выше:

@Controller
public class SocketController {

@MessageMapping("/secured/chat")
@SendTo("/secured/history")
public OutputMessage send(Message msg) throws Exception {
return new OutputMessage(
msg.getFrom(),
msg.getText(),
new SimpleDateFormat("HH:mm").format(new Date()));
}
}

5. Та же политика происхождения

Политика того же источника требует, чтобы все взаимодействия с конечной точкой происходили из того же домена, в котором взаимодействие было инициировано.

Например, предположим, что ваша реализация WebSockets размещена на foo.com , и вы применяете ту же политику происхождения . Если пользователь подключается к вашему клиенту, размещенному на foo.com, а затем открывает другой браузер для bar.com , тогда bar.com не будет иметь доступа к вашей реализации WebSocket.

5.1. Переопределение той же политики происхождения

Spring WebSockets применяет ту же политику происхождения из коробки, в то время как обычные WebSockets этого не делают.

Фактически, Spring Security требует токена CSRF ( подделка межсайтовых запросов ) для любого допустимого типа сообщения CONNECT :

@Controller
public class CsrfTokenController {
@GetMapping("/csrf")
public @ResponseBody String getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return csrf.getToken();
}
}

Вызывая конечную точку по адресу /csrf , клиент может получить токен и пройти аутентификацию через уровень безопасности CSRF.

Однако ту же политику происхождения для Spring можно переопределить , добавив следующую конфигурацию в ваш AbstractSecurityWebSocketMessageBrokerConfigurer :

@Override
protected boolean sameOriginDisabled() {
return true;
}

5.2. STOMP, поддержка SockJS и параметры фрейма

Обычно STOMP используется вместе с SockJS для реализации клиентской поддержки Spring WebSockets.

SockJS по умолчанию настроен на запрет транспорта через HTML-элементы iframe . Это делается для предотвращения угрозы кликджекинга .

Тем не менее, есть определенные варианты использования, в которых может быть полезно разрешить iframe использовать транспорты SockJS . Для этого вы можете переопределить конфигурацию по умолчанию в WebSecurityConfigurerAdapter :

@Override
protected void configure(HttpSecurity http)
throws Exception {
http
.csrf()
//...
.and()
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests();
}

Обратите внимание, что в этом примере мы следуем той же политике происхождения , несмотря на то, что разрешаем транспорт через iframe .

6. Покрытие Oauth2

Поддержка Spring WebSockets, специфичная для Oauth2, стала возможной благодаря реализации покрытия безопасности Oauth2 в дополнение к стандартному покрытию WebSecurityConfigurerAdapter и путем его расширения . Вот пример того, как реализовать Oauth2.

Чтобы пройти аутентификацию и получить доступ к конечной точке WebSocket, вы можете передать Oauth2 access_token в параметр запроса при подключении от вашего клиента к серверному WebSocket.

Вот пример, демонстрирующий эту концепцию с использованием SockJS и STOMP:

var endpoint = '/ws/?access_token=' + auth.access_token;
var socket = new SockJS(endpoint);
var stompClient = Stomp.over(socket);

7. Заключение

В этом кратком руководстве мы показали, как повысить безопасность Spring WebSockets. Взгляните на справочную документацию Spring WebSocket и WebSocket Security , если вы хотите узнать больше об этой интеграции.

Как всегда, проверьте наш проект Github для примеров, используемых в этой статье.