Введение в безопасность и WebSockets
1. Вступление
Вprevious article мы показали, как добавить WebSockets в проект Spring MVC.
Здесь мы опишем, какadd security to Spring WebSockets in Spring MVC. Прежде чем продолжить, убедитесь, что у вас уже есть базовое покрытие Spring MVC Security - если нет, проверьтеthis article.
2. Maven Зависимости
Нам нужныtwo main groups of Maven dependencies для нашей реализации WebSocket.
Во-первых, давайте укажем всеобъемлющие версии Spring Framework и Spring Security, которые мы будем использовать:
5.0.6.RELEASE
5.0.6.RELEASE
Во-вторых, давайте добавим основные библиотеки Spring MVC и Spring Security, необходимые для реализации базовой аутентификации и авторизации:
org.springframework
spring-core
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework.security
spring-security-web
${spring-security.version}
org.springframework.security
spring-security-config
${spring-security.version}
Последние версииspring-core,spring-web,spring-webmvc,spring-security-web,spring-security-config можно найти на Maven Central.
Наконец, давайте добавим необходимые зависимости:
org.springframework
spring-websocket
${spring.version}
org.springframework
spring-messaging
${spring.version}
org.springframework.security
spring-security-messaging
${spring-security.version}
Вы можете найти последнюю версиюspring-websocket,spring-messaging иspring-security-messaging на Maven Central.
3. Базовая безопасность WebSocket
Специфичная для WebSocket безопасность с использованием библиотекиspring-security-messaging сосредоточена на классеAbstractSecurityWebSocketMessageBrokerConfigurer и его реализации в вашем проекте:
@Configuration
public class SocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
//...
}
КлассAbstractSecurityWebSocketMessageBrokerConfigurerprovides additional security coverage, предоставляемыйWebSecurityConfigurerAdapter.
Библиотекаspring-security-messaging - не единственный способ реализовать безопасность для WebSockets. Если мы будем придерживаться обычной библиотекиspring-websocket, мы сможем реализовать интерфейсWebSocketConfigurer и прикрепить перехватчики безопасности к нашим обработчикам сокетов.
Поскольку мы используем библиотекуspring-security-messaging, мы будем использовать подходAbstractSecurityWebSocketMessageBrokerConfigurer.
3.1. РеализацияconfigureInbound()
РеализацияconfigureInbound() - самый важный шаг в настройке подклассаAbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected void configureInbound(
MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**").authenticated()
.anyMessage().authenticated();
}
В то время какWebSecurityConfigurerAdapter позволяет указать различные требования к авторизации в масштабе приложения для разных маршрутов,AbstractSecurityWebSocketMessageBrokerConfigurer позволяет указать конкретные требования к авторизации для назначений сокетов.
3.2. Соответствие типа и назначения
MessageSecurityMetadataSourceRegistry позволяет нам указывать ограничения безопасности, такие как пути, роли пользователей и разрешенные сообщения.
Type matchers constrain which SimpMessageType are allowed и каким образом:
.simpTypeMatchers(CONNECT, UNSUBSCRIBE, DISCONNECT).permitAll()
Destination matchers constrain which endpoint patterns are accessible и каким образом:
.simpDestMatchers("/app/**").hasRole("ADMIN")
Subscribe destination matchers map a ListofSimpDestinationMessageMatcher instancesthat match on SimpMessageType.SUBSCRIBE:
.simpSubscribeDestMatchers("/topic/**").authenticated()
Вот полный списокall available methods for type and destination matching.
4. Защита маршрутов сокетов
Теперь, когда мы познакомились с базовой безопасностью сокетов и конфигурацией сопоставления типов, мы можем комбинировать безопасность сокетов, представления, STOMP (протокол обмена текстовыми сообщениями), брокеры сообщений и контроллеры сокетов, чтобы включить безопасные веб-сокеты в нашем приложении Spring MVC.
Во-первых, давайте настроим наши представления сокетов и контроллеры для базового покрытия Spring Security:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.example.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();
}
}
Давайте определимan example socket controller и конечную точку, для которой мы обеспечили безопасность выше:
@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. Политика единого происхождения
Same Origin Policy требует, чтобы все взаимодействия с конечной точкой происходили из того же домена, в котором взаимодействие было инициировано.
Например, предположим, что ваша реализация WebSockets размещена вfoo.com, а выenforcing same origin policy. Если пользователь подключается к вашему клиенту, размещенному наfoo.com, а затем открывает другой браузер наbar.com, тоbar.com не будет иметь доступа к вашей реализации WebSocket.
5.1. Отмена той же политики происхождения
Spring WebSockets сразу же применяет ту же политику происхождения, а обычные WebSockets - нет.
Фактически,Spring Security requires a CSRF (Cross Site Request Forgery) token для любого допустимого типа сообщения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.
ОднакоSame Origin Policy for Spring can be overridden, добавив следующую конфигурацию в вашAbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected boolean sameOriginDisabled() {
return true;
}
5.2. STOMP, поддержка SockJS и параметры фрейма
SockJS is configured to disallow transports through HTML iframe elements by default. This is to prevent the threat of clickjacking.
Однако есть определенные варианты использования, в которых разрешениеiframes использовать транспорты SockJS может быть полезным. Для этого вы можете изменить конфигурацию по умолчанию вWebSecurityConfigurerAdapter:
@Override
protected void configure(HttpSecurity http)
throws Exception {
http
.csrf()
//...
.and()
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests();
}
Обратите внимание, что в этом примере мы следуемSame Origin Policy, несмотря на то, что разрешены передачи черезiframes.
6. Покрытие Oauth2
Специфичная для Oauth2 поддержка Spring WebSockets стала возможной благодаря реализации покрытия безопасности Oauth2 в дополнение к вашему стандартному покрытиюWebSecurityConfigurerAdapter.Here’s - и за счет расширения - примера того, как реализовать Oauth2.
Чтобы аутентифицировать и получить доступ к конечной точке WebSocket, вы можете передать Oauth2access_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. Взгляните на справочную документацию SpringWebSocket иWebSocket Security, если вы хотите узнать больше об этой интеграции.
Как всегда, проверьтеour Github project на примеры, использованные в этой статье.