How to get endpoint path in ChannelInterceptor with spring boot stomp? - spring-boot

I am new in stomp use spring boot 2.1.2.RELEASE. I have multi endpoint and config a ChannelInterceptor to get some info.
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpoint1")
.addInterceptors(new IpHandshakeInterceptor())
.setAllowedOrigins(origin)
.withSockJS();
registry.addEndpoint("/endpoint2")
.addInterceptors(new IpHandshakeInterceptor())
.setAllowedOrigins(origin)
.withSockJS();
// other andpoint
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(myChannelInterceptor());
}
All endpoint use myChannelInterceptor(actually, i want endpoint use its own ChannelInterceptor), i want do thing in ChannelInterceptor by endpoint path.
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (endpoint.equals("endpoint1")) {
} else if (endpoint.equals("endpoint2")) {
}
}
How can i get endpoint info in ChannelInterceptor?

You can use:
In class IpHandshakeInterceptor write value to attributes map:
#Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpSession session = servletRequest.getServletRequest().getSession();
//add value to session attributes
map.put("endpoint", servletRequest.getURI().getPath());
}
// ... your logic ...
return true;
}
In your myChannelInterceptor read value from session attributes:
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
String endpoint=accessor.getSessionAttributes().get("endpoint").toString();
// ... your logic ...
return message;
}

You can get the destination topic and also user object (if you populated it earlier) in the SUBSCRIBE command like this:
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// authenticate request and populate user object
}
if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) {
authorizeRequest(accessor.getUser(), accessor.getDestination());
}
return message;
}

Related

Spring stomp session (StompHeaderAccessor accessor)

I'm loading the desired 'session' into the StompHeaderAccessor via a ChannelInterceptor.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// ... skip
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
/**
* session check
*/
if (accessor != null && Arrays.asList(StompCommand.CONNECT, StompCommand.SEND).contains(accessor.getCommand())) {
String token = StringUtil.fixNull(accessor.getFirstNativeHeader("Authorization"),"");
String cno = accessor.getFirstNativeHeader("cno");
HashMap<String, Object> param = new HashMap<>();
param.put("Authorization", token);
param.put("cno", cno);
if (!token.equals("")) {
accessor.setSessionAttributes(Collections.singletonMap("session", sessionService.getSession(param)));
}
}
return message;
}
}
}
And I want to use the session I put in the controller.
But I can't see the session I put in the controller's Accessor.
#MessageMapping("/inquiry")
public void publishInquiry(SimpMessageHeaderAccessor accessor) throws Exception {
Object session = accessor.getSessionAttributes().get("session");
System.out.println(session);
// null
Map<String, Object> sessionAttributes = accessor.getSessionAttributes();
System.out.println("sessionAttributes = " + sessionAttributes);
// sessionAttributes = {}
}
How do I use the session I put in the interceptor in the controller?

Read custom header value from the response

When I send request from the Soap UI under raw response tab I see the following result(find attachment). Now in AOP controller I want to read this header value which is marked as red. How it is possible? Thanks in advance.
In my application to send soap requests I have WebServiceTemplate. I applied custom interceptor WebServiceInterceptor (which implements ClientInterceptor interface) on this web service template. In overridden afterCompletion method, which injects MessageContext, I was able to take this property from the SaajMessageHeader.
Here is what code looks like:
#Configuration
public class MyWebServiceConfig {
#Bean(name = "myWSClient")
public WebServiceTemplate myWSClient() throws Exception {
WebServiceTemplate template = new WebServiceTemplate();
...
WebServiceInterceptor[] interceptors = { new WebServiceInterceptor() };
template.setInterceptors(interceptors);
return template;
}
private static class WebServiceInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
...
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
try {
SaajSoapMessage message = (SaajSoapMessage) messageContext.getResponse();
String []traceId = message.getSaajMessage().getMimeHeaders().getHeader("ITRACING_TRACE_ID");
if(traceId != null && traceId.length > 0){
process.setTraceId(traceId[0]);
}
} catch (Exception e) {
}
}
}

Create HttpServletResponse object in Zuul custom filter

I have a Zuul custom filter of type PRE_TYPE.
When I receive the request I want to prevent its routing and instead send a response, in this case a SOAP message, since I am simulating a web service response.
My custom filter:
#Component
public class CustomFilter extends ZuulFilter {
private ThreadLocal<byte[]> buffers;
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = getCurrentContext();
ctx.unset();
String s= "<soap:Envelope xmlns:......</soap:Envelope>";
}
#Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
}
I need to create a HttpServletResponse and fill it with my response and write it to the output stream, so the client receives that response.
How can I create the servletresponse object?
Try something like this:
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setSendZuulResponse(false);
ctx.setResponseBody("<soap:Envelope xmlns:......</soap:Envelope>");
ctx.setResponseStatusCode(...);
return null;
}

Spring MVC Websockets with STOMP - Authenticate against specific channels

Is there a way in AbstractWebSocketMessageBrokerConfigurer (Spring Boot) to intercept the registration of users to a specific channel?
I have a basic authentication done in registerStompEndpoints using a HandshakeHandler:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
HandshakeHandler handler = new DefaultHandshakeHandler() {
#Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
return () -> getPrincipal();
}
return principal;
}
};
registry.addEndpoint("/websocket")
.setHandshakeHandler(handler)
.setAllowedOrigins("*").withSockJS();
}
Now I would like to prevent this user from registering to '/topic/admin/news' if the user does not have the permission 'admin'. I'm not using Spring Security. I'd like to have an interceptor before the registration to a channel happens.
As an alternative, I'd like to use the SimpMessagingTemplate to only send out messages to users from the channel that have the permission. Is there a way to see what users are currently connected to my stomp-connection?
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new TopicSubscriptionInterceptor());
}
And the interceptor:
public class TopicSubscriptionInterceptor implements ChannelInterceptor {
private static Logger logger = org.slf4j.LoggerFactory.getLogger(TopicSubscriptionInterceptor.class);
#Override
public Message<?> postReceive(Message<?> message, MessageChannel chanenel) {
return message;
}
#Override
public void postSend(Message<?> message, MessageChannel chanel, boolean sent) {
}
#Override
public boolean preReceive(MessageChannel channel) {
return true;
}
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor headerAccessor= StompHeaderAccessor.wrap(message);
if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand()) && headerAccessor.getHeader("simpUser") !=null && headerAccessor.getHeader("simpUser") instanceof UsernamePasswordAuthenticationToken) {
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) headerAccessor.getHeader("simpUser");
if(!validateSubscription((User)userToken.getPrincipal(), headerAccessor.getDestination()))
{
throw new IllegalArgumentException("No permission for this topic");
}
}
return message;
}
private boolean validateSubscription(User principal, String topicDestination)
{
logger.debug("Validate subscription for {} to topic {}",principal.getUsername(),topicDestination);
//Validation logic coming here
return true;
}
}

How to add custom headers to STOMP CREATED message in Spring Boot application?

I'm trying to add custom headers to the STOMP 'CREATED' message, which is received by client at the first connection. Here is the function which connects to the WebSocket using STOMP JavaScript:
function connect() {
socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect('', '', function(frame) {
whoami = frame.headers['user-name'];
console.log(frame);
stompClient.subscribe('/user/queue/messages', function(message) {
console.log("MESSAGE RECEIVED:");
console.log(message);
showMessage(JSON.parse(message.body));
});
stompClient.subscribe('/topic/active', function(activeMembers) {
showActive(activeMembers);
});
});
}
This function prints the following to the browser's console:
body: ""
command: "CONNECTED"
headers: Object
heart-beat: "0,0"
user-name: "someuser"
version: "1.1"
And i want to add custom header so output must look like:
body: ""
command: "CONNECTED"
headers: Object
heart-beat: "0,0"
user-name: "someuser"
version: "1.1"
custom-header: "foo"
I have the following WebSocket configuration in my Spring Boot app.
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue", "/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat", "/activeUsers")
.withSockJS()
.setInterceptors(customHttpSessionHandshakeInterceptor());
}
...
#Bean
public CustomHttpSessionHandshakeInterceptor
customHttpSessionHandshakeInterceptor() {
return new CustomHttpSessionHandshakeInterceptor();
}
}
I have tried to register the 'HandshakeInterceptor' to set custom header, but it didn't work. Here is 'CustomHttpSessionHandshakeInterceptor':
CustomHttpSessionHandshakeInterceptor.java
public class CustomHttpSessionHandshakeInterceptor implements
HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest =
(ServletServerHttpRequest) request;
attributes.put("custom-header", "foo");
}
return true;
}
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Exception ex) { }
}
I have found this code snippet at https://dzone.com/articles/spring-boot-based-websocket
Can someone explain me why this approach does not work? Is there another way to set custom headers to the STOMP 'CREATED' message at server side in Spring Boot application?
Thanks!
Maybe it's too late, but better late than never ...
Server messages (e.g. CONNECTED) are immutable, means that they cannot be modified.
What I would do is register a client outbound interceptor and trap the connected message by overriding the preSend(...) method and build a new message with my custom headers.
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel)
{
LOGGER.info("Outbound channel pre send ...");
final StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
final StompCommand command = headerAccessor.getCommand();
if (!isNull(command)) {
switch (command) {
case CONNECTED:
final StompHeaderAccessor accessor = StompHeaderAccessor.create(headerAccessor.getCommand());
accessor.setSessionId(headerAccessor.getSessionId());
#SuppressWarnings("unchecked")
final MultiValueMap<String, String> nativeHeaders = (MultiValueMap<String, String>) headerAccessor.getHeader(StompHeaderAccessor.NATIVE_HEADERS);
accessor.addNativeHeaders(nativeHeaders);
// add custom headers
accessor.addNativeHeader("CUSTOM01", "CUSTOM01");
final Message<?> newMessage = MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders());
return newMessage;
default:
break;
}
}
return message;
}
#UPDATE:::
The interface needed is called ChannelInterceptor and to register your own implementation you need to add #Configuration annotated class
#Configuration
public class CustomMessageBrokerConfig extends WebSocketMessageBrokerConfigurationSupport
implements WebSocketMessageBrokerConfigurer{}
and override a method configureClientOutboundChannel as below
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
log.info("Configure client outbound channel started ...");
registration.interceptors(new CustomOutboundChannelInterceptor());
log.info("Configure client outbound channel completed ...");
}
Did you try it like this way? MessageHeaderAccessor has a setHeader method too.
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-authentication-token-based

Resources