I have seen a couple of threads about this issue, but none of them seem to really answer the question directly.
Background, I have spring security installed, working, and running smoothly in other parts of the application. My username is "developer".
Running on Java 7, Glassfish 4, Spring 4, and using Angular + StompJS
Let's get some code here:
package com.myapp.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
public final static String userDestinationPrefix = "/user/";
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp").withSockJS().setSessionCookieNeeded(true);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
//registry.enableStompBrokerRelay("/topic,/user");
registry.enableSimpleBroker("/topic", "/user");
registry.setUserDestinationPrefix(userDestinationPrefix);
}
}
Ok, now here is a controller, to send out stuff every 3 seconds:
import org.springframework.messaging.simp.SimpMessagingTemplate;
…
#Autowired
private SimpMessagingTemplate messagingTemplate;
…
#Scheduled(fixedDelay = 3000)
public void sendStuff ()
{
Map<String, Object> map = new HashMap<>();
map.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
System.out.print("Sending data! " + System.currentTimeMillis());
//messagingTemplate.convertAndSend("/topic/notify", "Public: " + System.currentTimeMillis());
messagingTemplate.convertAndSendToUser("developer", "/notify", "User: " + System.currentTimeMillis());
messagingTemplate.convertAndSendToUser("notYou", "/notify", "Mr Developer Should Not See This: " + System.currentTimeMillis());
}
And finally the JavaScript using SockJS
var client = new SockJS('/stomp');
var stomp = Stomp.over(client);
stomp.connect({}, function(s) {
//This should work
stomp.subscribe('/user/' + s.headers['user-name'] + '/notify', console.debug);
//This SHOULD NOT
stomp.subscribe('/user/notYou/notify', console.debug);
});
client.onclose = $scope.reconnect;
And finally, for kicks, the pom.xml
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
Here is what does work:
I can produce wonderfully communication back and forth between the client and the server
It's fast
messagingTemplate.convertAndSend and messagingTemplate.convertAndSendToUser
This is the problem (noted above): Anyone can subscribe to other users feeds.
Now, there are a few other versions of this floating around, I will list them below, and explain why the answers are all wrong:
What are the security issues around an open websocket connection?
Spring websocket with stomp security - every user can subscribe to any other users queue?
Websocket: How To Push A Message To A Target User
Here's the problem:
Look at messagingTemplate.convertAndSendToUser - All that does is add the "user prefix" and then the username provided and then use messagingTemplate.convertAndSend which does not apply security.
Then people say that "you need to use spring security just like everywhere else" - the problem here is A) that I am SENDING data to the client asynchronously, so B) I will be using this code completely outside of the user's session, possibly from a different user (say to send a notification to another logged in user).
Let me know if this is too closely related to a different post, but I this is a big problem for me and I wanted to do this justice.
I can get more details though if anyone needs more details.
New Spring Security 4x now fully support Web Socket, you can refer the link Preview Spring Security WebSocket Support
Or SpringSecuritySupportWebSocket.html in case you need a complete example,
I think you must make these changes:
1) You must not enableSimpleBroker for "/user" because it's a special queue handled automatically by the broker
2) if the server uses for example the annotation "#SendToUser("/queue/private")" the client must subscribe to the queue "/user/queue/private" : you must not prepend the username in the queue because it's a transparent operation handled by the broker
I'm sure this works correctly because I'm using it in my setup.
I've not tried with the convertAndSendToUser() method but since its semantic should be the same of the annotation, it should work too.
You can override configureInbound method in a JavaConfig class extending AbstractSecurityWebSocketMessageBrokerConfigurer.
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated() 1
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() 2
.simpDestMatchers("/app/**").hasRole("USER") 3
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") 4
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() 5
.anyMessage().denyAll(); 6
}
}
There, you can configure credentials to subscribe a channel, send messages or several other things, as mentioned in Spring WebSocket documntation https://docs.spring.io/spring-security/site/docs/current/reference/html/websocket.html#websocket-authorization
Related
My application contains rabbitmq,hazelcast,dynamodb,elastic I want build a mechanism that can tell me whenever any of my application get down I should be notified on an email.Can we include any service which i can track to get status of all application that are integrated in my spring boot application
I tried using try catch block but line of code got increased and it made my code very cumbersome as it difficult for me to add try catch at every method
Spring Boot actuator provides a lot of useful endpoints, one of which is the health endpoint. The health endpoint returns the health status of your application based on its dependencies (databases, third party APIs, ...).
There are already builtin health indicators for RabbitMQ, Hazelcast and Elastic. There is no builtin health indicator for DynamoDB as far as I know, but you can also write your own health indicator, as seen in this question.
Now, to send you an email there are a two options:
You can use an external program (eg. monitoring software) to regularly check the health actuator
You can write it as part of your Spring boot application
Using an external program
If you use an external program, you can make it consume the /actuator/health endpoint. To enable this endpoint, configure the following property:
management.endpoints.web.exposure.include=health
By default this endpoint only exposes a single aggregated status of all health indicators combined. If you want to individually see which service is down, you can show more details by setting the following property to always or when_authorized:
management.endpoint.health.show-details=always | when_authorized
The response will look something like this:
{
"status": "DOWN",
"rabbit": {
"status": "DOWN",
"error": "org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused (Connection refused)"
}
}
Writing your own
You can write a scheduled task by using the #Scheduled annotation to regularly check the health status. To obtain the health status, you can autowire HealthEndpoint as seen in this question.
Then you can send an email with Spring.
#Component
public class HealthEmailSender {
private final HealthEndpoint healthEndpoint;
private final JavaMailSender mailSender;
// TODO: Implement constructor to autowire healthEndpoint + mailSender
#Scheduled(fixedRate = 10 * 60 * 1000) // Check once every 10 minutes
public void sendEmailWhenBadHealth() {
if (isHealthDown()) {
sendMail();
}
}
private boolean isHealthDown() {
return Status.DOWN.equals(healthEndpoint.health().getStatus());
}
private void sendMail() {
MimeMessage message = mailsender.createMimeMessage();
// TODO: Set from / to / title / content
mailSender.send(message);
}
}
This code snippet above would send an e-mail as soon as any health indicator goes down (not only from the services you mentioned).
To obtain the health status of one of the services you're interested in, you can use healthEndpoint.healthForPath("rabbit").getStatus().
I've two microservices interacting with each other via Kafka, that is the one publishes messages while the other consumes them. Both the publisher and the consumer run on Quarkus (1.12.0.Final) and use reactive messaging and Mutiny.
Producer:
package myproducer;
import myavro.MyAvro;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.eclipse.microprofile.reactive.messaging.Message;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.concurrent.CompletableFuture;
#ApplicationScoped
public class Publisher {
#Channel("mytopic")
#Inject
public Emitter<MyAvro> myTopic;
#Override
public Uni<Void> publish(MyModel model) {
MyAvro avro = MyModelMapper.INSTANCE.modelToAvro(model);
return Uni.createFrom().emitter(e -> myTopic.send(Message.of(avro)
.addMetadata(toOutgoingKafkaRecordMetadata(avro))
.withAck(() -> {
e.complete(null);
return CompletableFuture.completedFuture(null);
})));
}
}
Consumer:
package myconsumer;
import myavro.MyAvro;
import io.smallrye.mutiny.Uni;
import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecord;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import javax.enterprise.context.ApplicationScoped;
#ApplicationScoped
public class Consumer {
#Incoming("mytopic")
public Uni<Void> consume(IncomingKafkaRecord<String, MyAvro> message) {
MyModel model = MyModelMapper.INSTANCE.avroToModel(message.getPayload());
return ...;
}
}
Dependencies:
include among others the artefacts
quarkus-smallrye-reactive-messaging-kafka
quarkus-resteasy-mutiny
quarkus-smallrye-opentracing
quarkus-mutiny
opentracing-kafka-client
Quarkus configuration (application.properties):
includes among others
quarkus.jaeger.service-name=myservice
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss} %-5p traceId=%X{traceId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n
mp.messaging.incoming.mytopic.topic=abc
mp.messaging.incoming.mytopic.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.mytopic.value.deserializer=io.confluent.kafka.serializers.KafkaAvroDeserializer
...
mp.messaging.incoming.mytopic.interceptor.classes=io.opentracing.contrib.kafka.TracingConsumerInterceptor
With this setup no traceId or spanId is logged at all (even though they should according to Quarkus' "Using OpenTracing" guide). Only after adding #org.eclipse.microprofile.opentracing.Traced a traceId and a spanId is set, but both are completely unrelated to each other on the producer and the consumer.
I checked my opentracing configuration against the beforementioned Quarkus' guide "Using OpenTracing" but found no hints for a misconfiguration on my side.
After reading discussions about issues in some Quarkus extensions relying on ThreadLocals when using with Mutiny I added the artefact quarkus-smallrye-context-propagation to my dependencies, but to no avail.
I suspect that the issue might be related to https://github.com/quarkusio/quarkus/issues/15182, though there it's about reactive routes instead of reactive messaging.
Any ideas?
This issue is not easy to solve, first I will try to explain what happens.
OpenTracing has the concepts of transactions and spans. A span is a block of execution (a method, a database call, a send to a Kafka topic), whereas a transaction is a distributed process that span multiple components (a group of spans).
The issue here is that, each time a span is created, it didn't find any OpenTracing transaction so it creates a new one. This is why none of your spans are correlated to each others.
In OpenTracing, when you create a span, you'll create it based on a span context. Each OpenTracing integration will creates a span context based on the extension technology (I didn't find a better term), for example, HTTP span context is based on HTTP headers and Kafka span context is based on Kafka Headers.
So, to correlate two spans, you need to have the span context created with some context from the underlying technology providing the right OpenTracing ID.
For example, to correlate two Kafka spans, you need to have a uber-trace-id header (this is the default name of the OpenTracing id in Jaeger) with the trace identifier (see tracespan-identity for the format of this header).
Knowing this, there is multiple things to do.
First, you need to add an uber-trace-id Kafka header inside your outgoing message in your #Traced method to correlate the span from the method with the span created inside the Kafka producer interceptor.
Tracer tracer = GlobalTracer.get(); // you can also inject it
JaegerSpanContext spanCtx = ((JaegerSpan)tracer.activeSpan()).context();
// uber-trace-id format: {trace-id}:{span-id}:{parent-span-id}:{flags}
//see https://www.jaegertracing.io/docs/1.21/client-libraries/#tracespan-identity
var uberTraceId = spanCtx.getTraceId() + ":" +
Long.toHexString(spanCtx.getSpanId()) + ":" +
Long.toHexString(spanCtx.getParentId()) + ":" +
Integer.toHexString(spanCtx.getFlags());
headers.add("uber-trace-id", openTracingId.getBytes());
Then, you need to correlate your #Traced method with the span from the incoming message, if any. For this, the easiest way is to add a CDI interceptor that will try to create a span context for all methods annotated with #Traced based on the method parameters (it will search for a Message parameter). For this to work, this interceptor needs to be executed before the OpenTracing interceptor, and sets the span context in the interceptor context.
This is our interceptor implementation, feel free to use it or adapt it for your needs.
public class KafkaRecordOpenTracingInterceptor {
#AroundInvoke
public Object propagateSpanCtx(InvocationContext ctx) throws Exception {
for (int i = 0 ; i < ctx.getParameters().length ; i++) {
Object parameter = ctx.getParameters()[i];
if (parameter instanceof Message) {
Message message = (Message) parameter;
Headers headers = message.getMetadata(IncomingKafkaRecordMetadata.class)
.map(IncomingKafkaRecordMetadata::getHeaders)
.get();
SpanContext spanContext = getSpanContext(headers);
ctx.getContextData().put(OpenTracingInterceptor.SPAN_CONTEXT, spanContext);
}
}
return ctx.proceed();
}
private SpanContext getSpanContext(Headers headers) {
return TracingKafkaUtils.extractSpanContext(headers, GlobalTracer.get());
}
}
This code uses both the Quarkus OpenTracing extension and the Kafka OpenTracing contrib library.
With both the correlation of outgoing message thanks to the addition of the OpenTracing Kafka Header created from the current span context, and the creation of a context from incoming message's header, the correlation should happens in any case.
I am trying to start a mock a server from Java and keep it running to receive incoming requests from other sources (Postman, CURL, etc).
I have tried the Junit approach, but when the unit tests finishes, the server is shutdown.
On the other hand, running the standalone version
http://www.mock-server.com/mock_server/running_mock_server.html#running_from_command_line
keeps the mock server running.
I would like to achieve the same thing, but from the Java code.
The question is, how may I make it run and stay running?
Thanks
So you need an HTTP server for non-testing purposes? I'd try with Spring, something like:
#RestController
public class CatchAllController {
#RequestMapping("/**")
public String catchAll(HttpServletRequest request) {
return request.getRequestURI();
}
}
There is an example on that same page (paragraph "Client API - starting and stopping"). This code works for me:
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import org.mockserver.integration.ClientAndServer;
public class Checker {
public static void main(String[] args) {
ClientAndServer mockServer = startClientAndServer(1080);
}
}
You have to call
mockServer.stop();
later to stop it.
You will need the following maven dependency:
<!-- mockserver -->
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.5.1</version>
</dependency>
With the example provided by spring.io and http://www.baeldung.com/websockets-spring is helped to create a websocket connection between client and server, but my case is.
- Some one is creating message from UI that is passed to Spring controller (Separate controller).
- From this controller I need to notify/send/broadcast this message to all connected clients.
- How the message is passed to handler from controller where message is received.
I also refereed WebSocket with Sockjs & Spring 4 but without Stomp here and the same question is posted.
Can some one help me here, Thanks in advance !!
I actually write for Baeldung too and am currently writing a small article about how to add security to websockets in Spring! There are just a few steps you need to do to get this all working!
Backend-wise (since you said the UI was already done or being built, I'll just focus on the backend here), it really involves three parts: (1) the necessary POJO's, the controller, and the configuration.
Your POJO's will be very simple - here we just use Greeting and Message which specify a name and basic text data type (I'll skip over this here to save space but you can see it in the resource below).
Your controller will look like this:
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
Take a look at the annotations - those are really what set this controller apart from say a normal REST controller.
And your configuration looks like this - again take a look at the annotations - particularly '#EnableWebSocketMessageBroker' - and the class 'AbstractWebSocketMessageBrokerConfigurer':
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
A look at this great resource too: https://spring.io/guides/gs/messaging-stomp-websocket/
Today I've searched a couple of hours for an implementation or tutorial in how to keep track of websocket connections in Spring.
I've done the (very good) Spring tutorial about websockets and STOMP.
link here
So what's my setup, I have an Ionic Hybrid app with an Spring backend and I want to send a notification to the client whenever a new notification-event arises in the backend. All this code is already implemented and the connection works, but right now there is no way to specify where the notifications need to go to.
There is no tutorial or explanation on this matter that follows the structure in the Spring tutorial (at least not after 5 hours of research) and I am a little overwhelmed by all the information about websockets and security on the web. (I've been learning about websockets for just 2 days)
So for all that been before me and will come after me, I think it can be very usefull to have a compact and lightweight answer following the structure taught by the Spring Tutorial.
I've found this unanswered question on StackOverflow about the same problems as I have, so I'm sure this questions will prove it's worth.
TL;DR
How to implement a list in the backend that keeps track of the connections based on the Spring WebSocket Tutorial?
How to send data from the client to the backend when the connection is established? (for example a userid or token)
So I figured it out myself.
My notifications have a recipient id (the user id where the notifications needs to be send to)
So I'm going to send to '/ws-user/'+id+'/greetings' where the id is the user that is logged in.
On the clientside this is fairly easy to achieve.
var stompClient = null;
// init
function init() {
/**
* Note that you need to specify your ip somewhere globally
**/
var socket = new SockJS('http://127.0.0.1:9080/ws-notification');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
/**
* This is where I get the id of the logged in user
**/
barService.currentBarAccountStore.getValue().then(function (barAccount) {
subscribeWithId(stompClient,barAccount.user.id);
});
});
}
/**
* subscribe at the url with the userid
**/
function subscribeWithId(stompClient,id){
stompClient.subscribe('/ws-user/'+id+'/greetings', function(){
showNotify();
});
}
/**
* Broadcast over the rootscope to update the angular view
**/
function showNotify(){
$rootScope.$broadcast('new-notification');
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
// setConnected(false);
console.log("Disconnected");
}
Next up we add "setUserDestinationPrefix" to the MessageBrokerRegistry in the WebSocketConfig.java class :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final static String userDestinationPrefix = "/ws-user/";
#Override
public void configureMessageBroker(MessageBrokerRegistry config){
config.enableSimpleBroker("/ws-topic","/ws-user");
config.setApplicationDestinationPrefixes("/ws-app");
config.setUserDestinationPrefix(userDestinationPrefix);
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-notification").setAllowedOrigins("*").withSockJS();
}
}
Note that I'm using internal RestTemplate calls to access my controllermethod that sends out a notification to the subscribed client. This is done by a Event Consumer class (ask to see code, it's just to trigger controller function, could be done differently)
#RequestMapping(value = "/test-notification", method = RequestMethod.POST)
public void testNotification(#RequestBody String recipientId) throws InterruptedException {
this.template.convertAndSendToUser(recipientId,"/greetings", new Notify("ALERT: There is a new notification for you!"));
}
Please review my code and warn me if you see any issues and/or security concerns.
For user based delivery in websocket you can use Principle objects with spring security. Here is a nice example implemented:
https://github.com/rstoyanchev/spring-websocket-portfolio
Spring security will check for SAME ORIGIN and from your client you can send the stomp header with used-id specified.
Hope this might help you.
Take a look at this answer: How to get all active sessions in Spring 5 WebSocket API?
You can retrieve connected users using Spring's SimpUserRegistry API.