websockets - websocket spring boot angular 4
Cómo obtener/configurar los atributos de principal y sesión de Spring 4 stomp websocket methods (2)
Estoy haciendo experimentos con websockets de Spring 4 y stomp , y me cuesta entender cómo obtener / configurar el usuario actual y otros atributos de sesión en un método de manejo de mensajes anotado con @MessageMapping
.
La documentación dice que los métodos de manejo de mensajes pueden tomar un Principal como argumento, y encontré que Spring recupera el principal llamando a getUserPrincipal()
en la sesión de socket nativo y luego asociado con la sesión de socket, pero no he encontrado cualquier forma de personalizar fácilmente este comportamiento, aparte de escribir un filtro de servlet y envolver la solicitud original en un contenedor que devuelve el principal que se encuentra en mi cookie.
Entonces mis preguntas son:
- Cómo configurar manualmente el principal en la sesión de socket, cuando el cliente se conecta (tengo esta información gracias a una cookie personalizada, y no uso la seguridad de Spring).
- Si 1 no es posible, ¿cómo agregar atributos adicionales a la sesión de socket cuando el cliente se conecta?
- ¿Cómo acceder a la sesión de socket y sus atributos desde un método de manejo de mensajes?
- ¿Hay alguna forma de acceder al inicio de sesión y a la contraseña que envía el navegador en el momento de la conexión? Parece que son completamente ignorados por Spring y no son accesibles.
Esto es imposible por el momento (primavera 4.0). Se ha abierto (y considerado) un problema en Spring: https://jira.springsource.org/browse/SPR-11228
ACTUALIZACIÓN: con Spring 4.1 es posible configurar al usuario en el saludo de manos para # 1 desde arriba. Según la documentación de Spring , puede crear una nueva clase que amplíe DefaultHandshakeHandler y anule el método de determinar el usuario. Además, también puede crear un filtro de seguridad que establezca el principal también si tiene un token. Implementé el segundo yo mismo e incluí un código de muestra para ambos a continuación.
Para # 2 y # 3 no creo que todavía sea posible. Para # 4 Spring ignora intencionalmente estos por la documentación aquí .
CÓDIGO DE MUESTRA PARA DefaultCLINKHandshakeHandler SUBCLASS:
@Configuration
@EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
public class MyHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// add your own code to determine the user
return null;
}
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());
}
}
CÓDIGO DE MUESTRA PARA EL FILTRO DE SEGURIDAD:
public class ApplicationSecurityTokenFilter extends GenericFilterBean {
private final static String AUTHENTICATION_PARAMETER = "authentication";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
// check to see if already authenticated before trying again
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
UsernamePasswordAuthenticationToken token = extractToken(request);
// dump token into security context (for authentication-provider to pick up)
if (token != null) { // if it exists
SecurityContextHolder.getContext().setAuthentication(token);
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
UsernamePasswordAuthenticationToken authenticationToken = null;
// do what you need to extract the information for a token
// in this example we assume a query string that has an authenticate
// parameter with a "user:password" string. A new UsernamePasswordAuthenticationToken
// is created and then normal authentication happens using this info.
// This is just a sample and I am sure there are more secure ways to do this.
if (request.getQueryString() != null) {
String[] pairs = request.getQueryString().split("&");
for (String pair : pairs) {
String[] pairTokens = pair.split("=");
if (pairTokens.length == 2) {
if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
String[] tokens = pairTokens[1].split(":");
if (tokens.length == 2) {
log.debug("Using credentials: " + pairTokens[1]);
authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
}
}
}
}
}
return authenticationToken;
}
}
// set up your web security for the area in question
@Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
.addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic() // leave this if you want non web browser clients to connect and add an auth header
.and()
.csrf().disable();
}
}
** NOTA: ** NO declare su filtro como un frijol. Si lo hace, también se recogerá (al menos usando Spring Boot) en los filtros genéricos para que se active en cada solicitud.