Introdução à segurança e WebSockets
1. Introdução
Em aprevious article, mostramos como adicionar WebSockets a um projeto Spring MVC.
Aqui, descreveremos comoadd security to Spring WebSockets in Spring MVC. Antes de continuar, certifique-se de que você já possui a cobertura de segurança Spring MVC básica - se não, verifiquethis article.
2. Dependências do Maven
Precisamos detwo main groups of Maven dependencies para a implementação do WebSocket.
Primeiro, vamos especificar as versões abrangentes do Spring Framework e Spring Security que usaremos:
5.0.6.RELEASE
5.0.6.RELEASE
Em segundo lugar, vamos adicionar as principais bibliotecas Spring MVC e Spring Security necessárias para implementar autenticação e autorização básicas:
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}
As versões mais recentes despring-core,spring-web,spring-webmvc,spring-security-web,spring-security-config podem ser encontradas no Maven Central.
Por último, vamos adicionar as dependências necessárias:
org.springframework
spring-websocket
${spring.version}
org.springframework
spring-messaging
${spring.version}
org.springframework.security
spring-security-messaging
${spring-security.version}
Você pode encontrar a versão mais recente despring-websocket,spring-messaging espring-security-messaging no Maven Central.
3. Segurança WebSocket Básica
A segurança específica do WebSocket usando a bibliotecaspring-security-messaging centra-se na classeAbstractSecurityWebSocketMessageBrokerConfigurer e sua implementação em seu projeto:
@Configuration
public class SocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
//...
}
A classeAbstractSecurityWebSocketMessageBrokerConfigurerprovides additional security coverage fornecida porWebSecurityConfigurerAdapter.
A bibliotecaspring-security-messaging não é a única maneira de implementar segurança para WebSockets. Se ficarmos com a bibliotecaspring-websocket comum, podemos implementar a interfaceWebSocketConfigurer e anexar interceptores de segurança aos nossos manipuladores de soquete.
Como estamos usando a bibliotecaspring-security-messaging, usaremos a abordagemAbstractSecurityWebSocketMessageBrokerConfigurer.
3.1. ImplementandoconfigureInbound()
A implementação deconfigureInbound() é a etapa mais importante na configuração da subclasseAbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected void configureInbound(
MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**").authenticated()
.anyMessage().authenticated();
}
EnquantoWebSecurityConfigurerAdapter permite que você especifique vários requisitos de autorização em todo o aplicativo para rotas diferentes,AbstractSecurityWebSocketMessageBrokerConfigurer permite que você especifique os requisitos de autorização específicos para destinos de soquete.
3.2. Correspondência de tipo e destino
MessageSecurityMetadataSourceRegistry nos permite especificar restrições de segurança como caminhos, funções de usuário e quais mensagens são permitidas.
Type matchers constrain which SimpMessageType are allowed e de que forma:
.simpTypeMatchers(CONNECT, UNSUBSCRIBE, DISCONNECT).permitAll()
Destination matchers constrain which endpoint patterns are accessible e de que forma:
.simpDestMatchers("/app/**").hasRole("ADMIN")
Subscribe destination matchers map a ListofSimpDestinationMessageMatcher iinstânciasthat match on SimpMessageType.SUBSCRIBE:
.simpSubscribeDestMatchers("/topic/**").authenticated()
Aqui está a lista completa deall available methods for type and destination matching.
4. Protegendo Rotas de Soquete
Agora que conhecemos a segurança de soquete básica e a configuração de correspondência de tipo, podemos combinar segurança de soquete, visualizações, STOMP (um protocolo de mensagem de texto), corretores de mensagem e controladores de soquete para habilitar WebSockets seguros em nosso aplicativo Spring MVC.
Primeiro, vamos configurar nossas visualizações de soquete e controladores para cobertura básica do 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")
//...
}
}
Em segundo lugar, vamos configurar o destino real da mensagem com os requisitos de autenticação:
@Configuration
public class SocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**").authenticated()
.anyMessage().authenticated();
}
}
Agora, em nosso WebSocketMessageBrokerConfigurer,, podemos registrar a mensagem real e os endpoints 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();
}
}
Vamos definiran example socket controllere endpoint para o qual fornecemos cobertura de segurança acima:
@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. Política de Mesma Origem
OSame Origin Policy requer que todas as interações com um endpoint devem vir do mesmo domínio onde a interação foi iniciada.
Por exemplo, suponha que sua implementação WebSockets esteja hospedada emfoo.com e você sejaenforcing same origin policy. Se um usuário se conectar ao seu cliente hospedado emfoo.come abrir outro navegador embar.com,bar.com não terá acesso à sua implementação WebSocket.
5.1. Substituindo a Política da Mesma Origem
Spring WebSockets impõe a mesma política de origem fora da caixa, enquanto WebSockets comuns não.
Na verdade,Spring Security requires a CSRF (Cross Site Request Forgery) token para qualquer tipo de mensagemCONNECT válido:
@Controller
public class CsrfTokenController {
@GetMapping("/csrf")
public @ResponseBody String getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return csrf.getToken();
}
}
Chamando o endpoint em/csrf, um cliente pode adquirir o token e autenticar por meio da camada de segurança CSRF.
No entanto, oSame Origin Policy for Spring can be overridden adicionando a seguinte configuração ao seuAbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected boolean sameOriginDisabled() {
return true;
}
5.2. STOMP, suporte SockJS e opções de quadro
É comum usarSTOMP junto comSockJS para implementar o suporte do lado do cliente para Spring WebSockets.
SockJS is configured to disallow transports through HTML iframe elements by default. This is to prevent the threat of clickjacking.
No entanto, há certos casos de uso em que permitir queiframes aproveite os transportes SockJS pode ser benéfico. Para fazer isso, você pode substituir a configuração padrão emWebSecurityConfigurerAdapter:
@Override
protected void configure(HttpSecurity http)
throws Exception {
http
.csrf()
//...
.and()
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests();
}
Observe que, neste exemplo, seguimosSame Origin Policy, apesar de permitir transportes poriframes.
6. Cobertura Oauth2
O suporte específico de Oauth2 para Spring WebSockets é possível implementando a cobertura de segurança Oauth2 além de - e estendendo - suaWebSecurityConfigurerAdapter cobertura padrão.Here’s um exemplo de como implementar Oauth2.
Para autenticar e obter acesso a um endpoint WebSocket, você pode passar um Oauth2access_token em um parâmetro de consulta ao conectar-se do cliente ao WebSocket back-end.
Aqui está um exemplo que demonstra esse conceito usando SockJS e STOMP:
var endpoint = '/ws/?access_token=' + auth.access_token;
var socket = new SockJS(endpoint);
var stompClient = Stomp.over(socket);
7. Conclusão
Neste breve tutorial, mostramos como adicionar segurança ao Spring WebSockets. Dê uma olhada na documentação de referência deWebSocketeWebSocket Security do Spring para saber mais sobre esta integração.
Como sempre, verifiqueour Github project para exemplos usados neste artigo.