How to handle JWT Authentication with Spring when implementing a CQRS pattern? - java

Using the latest Spring Cloud and Spring Boot, I've got a micro services layout with a Zuul gateway. At the moment when a user sends a get request their JWT token gets added to the request and that goes off to the microservice where they're authenticated and things go as usual. This all works perfectly.
Where I'm a little stuck is when handling POST/PATCH/DELETE requests. These don't go directly to the microservice they're destined for, but instead go into a messaging queue. The queue contains a simple POJO with a task and information about the task to perform, along with the users JWT.
When the receiving microservice picks up the message from the queue and starts processing it, the user isn't technically logged into the microservice like they are with a GET request. This makes it hard to do things that require knowing who the user is. Sure each time I need to know who the person is I can look them up, but that seems clunky.
I've thought about creating a REST controller for the POST/PATCH/DELETE commands and having the queue listener just call itself from these, adding the token from the task. This would effectively be the same as a GET request as far as Spring security would care.
Is this the proper pattern? Or is there a simple programmatically way to log a user in with a JWT? I've seen a few examples using Username/Passwords but not sure how to transcribe that to using a JWT.

Thank you Andy Brown for the confirmation I wasn't completely nuts doing it this way. For anyone interested it's a very simple solution that looks like this:
The queue service is just listening for events (In this case from AWS SQS) and as soon as an event comes through it gets fired off to a command controller for processing.
#Service
#EnableSqs
public class QueueListener {
private final static String SERVICE = "http://instance-service/instances";
#Autowired
private JsonTransformService jsonTransformService;
#Autowired
private RestTemplate restTemplate;
#MessageMapping("${queues.instanceEvents}")
public void instanceCommandHandler(String payload) {
// Transform the payload to the object so we can get the preset JWT
Instance instance = jsonTransformService.read(Instance.class, payload);
// Load the JWT into the internal request header, without this a 403 is thrown
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + instance.getUserToken());
HttpEntity<String> instanceEntity = new HttpEntity<>(payload, headers);
// Decide and set where to fire the request to
String endpoint;
switch (instance.getSwordfishCommand()) {
case "start": {
endpoint = "/start";
break;
}
case "stop": {
endpoint = "/stop";
break;
}
case "create": {
endpoint = "/create";
break;
}
case "reboot": {
endpoint = "/reboot";
break;
}
case "terminate": {
endpoint = "/terminate";
break;
}
default: {
endpoint = "/error";
}
}
// Fire the initial string payload through to the correct controller endpoint
restTemplate.exchange(SERVICE + endpoint, HttpMethod.POST, instanceEntity, String.class);
}
}
And a very simple REST controller which actions the tasks
#RestController
#RequestMapping("/instances")
public class InstanceCommandController {
#Autowired
private EC2Create ec2Create;
#Autowired
private EC2Start ec2Start;
#Autowired
private EC2Stop ec2Stop;
#Autowired
private EC2Reboot ec2Reboot;
#Autowired
private EC2Terminate ec2Terminate;
#Autowired
private JsonTransformService jsonTransformService;
#PostMapping("/create")
public void create(#RequestBody String payload) {
ec2Create.process(jsonTransformService.read(Instance.class, payload));
}
#PostMapping("/start")
public void start(#RequestBody String payload) {
ec2Start.process(jsonTransformService.read(Instance.class, payload));
}
#PostMapping("/stop")
public void stop(#RequestBody String payload) {
ec2Stop.process(jsonTransformService.read(Instance.class, payload));
}
#PostMapping("/reboot")
public void reboot(#RequestBody String payload) {
ec2Reboot.process(jsonTransformService.read(Instance.class, payload));
}
#PostMapping("/terminate")
public void terminate(#RequestBody String payload) {
ec2Terminate.process(jsonTransformService.read(Instance.class, payload));
}
}
This follows the CQRS pattern very well while still authenticating the user on each call. For me this is very useful as I have a AmazonEC2Async client which makes use of the users own access and secret token in each request.
Cheers for the help!

Related

Forward request headers to multiple service calls in Spring + Netflix + Feign

I have a bunch of intermediate and core services within my application. All services are Spring Boot and using Netflix Library. When a user requests information, the request will/might pass other services in the chain eg:
Client <-> Zuul <-> Service B <-> Service A
I have configured all services (A and B) to be ResourceServer so that every access needs to be authenticated. When requesting an access token (From a Spring Security Server) and use it to request information directly from Service A, everything works fine. When I use the same token to access information from Service B (which needs Service A down the line) I get an "HTTP 401: Full authentification is required" error. Service B uses a FeignClient to call Service A.
After some debugging, I found out, that the Authorization-Header is not passed from Service B to Service A. Service B checks the token itself correctly, grants access to the method and tries to perform the request of Service A.
I tried a RequestInterceptor but without any success (Error "Scope 'request' is not active for the current thread")
#Component
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
private final OAuth2ClientContext oauth2ClientContext;
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext) {
Assert.notNull(oauth2ClientContext, "Context can not be null");
this.oauth2ClientContext = oauth2ClientContext;
}
#Override
public void apply(RequestTemplate template) {
if (template.headers().containsKey(AUTHORIZATION_HEADER)) {
...
} else if (oauth2ClientContext.getAccessTokenRequest().getExistingToken() == null) {
...
} else {
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE,
oauth2ClientContext.getAccessTokenRequest().getExistingToken().toString()));
}
}
}
This is an example proxy function that uses the FeignClient:
#Autowired
private CategoryClient cat;
#HystrixCommand(fallbackMethod = "getAllFallback", commandProperties = {#HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2") })
#GetMapping("/category")
public ResponseEntity<List<Category>> getAll() {
try {
ResponseEntity<List<Category>> categories = this.cat.getAll();
...
return categories;
} catch(Exception e) {
...
}
}
Is there any working solution to pass the Authorization-Header from the proxy function to the FeignClient so that Service A will receive the header and can do its own auth check with it?
Found a working solution. I still don't know if this is the "best" way to do it and if anyone got a better solution I'd be happy if you share it. But for now, this is working as expected:
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
requestTemplate.header("Authorization", "Bearer " + details.getTokenValue());
}
};
}

How to implement "load balancer" using spring boot?

Depends on request body content I need to redirect http requests to URL_1 or URL_2.
I started controller implementation:
#RestController
public class RouteController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public HttpServletResponse route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
//send request to URL_1 and get response
} else {
//send request to URL_2 and get response
}
}
}
Request might be GET or POST ot PUT or PATCH etc.
Could you help me to write that code?
I've asked a somehow similar question a while ago. Plea see Server side redirect for REST call for more context.
The best way (to my current understanding) you could achieve this is by manually invoking the desired endpoints from your initial endpoint.
#RestController
public class RouteController {
#Value("${firstUrl}")
private String firstUrl;
#Value("${secondUrl}")
private String secondUrl;
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public void route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
restTemplate.exchange(firstUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params));
} else {
restTemplate.exchange(secondUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params))
}
}
}
Example implementation for getHttpMethod:
public HttpMethod getHttpMethod(HttpServletRequest request) {
return HttpMethod.valueOf(request.getMethod());
}
Similar implementations for getHttpEntity, getResponseClass and getParams. They are used for converting the data from the HttpServletRequest request to the types required by the exchange method.
There seem to be a lot of better ways of doing this for a Spring MVC app, but I guess that it does not apply to your context.
Another way you could achieve this would be defining your own REST client and adding the routing logic there.

Micronaut security fails to "secure"

I have a simple Micronaut- based "hello world" service that has a simple security built in (for the sake of testing and illustrating the Micronaut security). The controller code in the service that implements the hello service is provided below:
#Controller("/hello")
public class HelloController
{
public HelloController()
{
// Might put some stuff in in the future
}
#Get("/")
#Produces(MediaType.TEXT_PLAIN)
public String index()
{
return("Hello to the World of Micronaut!!!");
}
}
In order to test the security mechanism, I have followed the Micronaut tutorial instructions and created a security service class:
#Singleton
public class SecurityService
{
public SecurityService()
{
// Might put in some stuff in the future
}
Flowable<Boolean> checkAuthorization(HttpRequest<?> theReq)
{
Flowable<Boolean> flow = Flowable.fromCallable(()->{
System.out.println("Security Engaged!");
return(false); <== The tutorial says return true
}).subscribeOn(Schedulers.io());
return(flow);
}
}
It should be noted that, in a departure from the tutorial, the flowable.fromCallable() lambda returns false. In the tutorial, it returns true. I had assumed that a security check would fail if a false is returned, and that a failure would cause the hello service to fail to respond.
According to the tutorials, in ordeer to begin using the Security object, it is necessary to have a filter. The filter I created is shown below:
#Filter("/**")
public class HelloFilter implements HttpServerFilter
{
private final SecurityService secService;
public HelloFilter(SecurityService aSec)
{
System.out.println("Filter Created!");
secService = aSec;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain)
{
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.doOnNext(res->{
System.out.println("Responding!");
});
return(resp);
}
}
The problem occurs when I run the microservice and access the Helo world URL. (http://localhost:8080/hello) I cannot cause the access to the service to fail. The filter catches all requests, and the security object is engaged, but it does not seem to prevent access to the hello service. I do not know what it takes to make the access fail.
Can someone help on this matter? Thank you.
You need to change request in your filter when you no have access to resource or process request as usual. Your HelloFilter looks like this:
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain) {
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.switchMap((authResult) -> { // authResult - is you result from SecurityService
if (!authResult) {
return Publishers.just(HttpResponse.status(HttpStatus.FORBIDDEN)); // reject request
} else {
return theChain.proceed(theReq); // process request as usual
}
})
.doOnNext(res -> {
System.out.println("Responding!");
});
return (resp);
}
And in the last - micronaut has security module with SecurityFilter, you can use #Secured annotations or write access rules in configuration files more examples in the doc

Spring - Rest controller handles json input as null string

While playing around with the facebook messenger api I created a simple REST controller
#RestController
public class ChatController
{
private static final Logger LOG = LoggerFactory.getLogger(ChatController.class);
#RequestMapping(value="/webhook", method=RequestMethod.POST, consumes="application/json")
public String onWebhookEvent(String event)
{
LOG.info("Received event {}",event);
return "test";
}
}
However, when I POST the following json to the the /webhook endpoint the event input is logged as null ("Received event null")
{"object":
"page",
"entry":[
{
"id":43674671559,
"time":1460620433256,
"messaging":[
{"sender":{"id":123456789},
"recipient":{"id":987654321},
"timestamp":1460620433123,
"message":{"mid":"mid.1460620432888:f8e3412003d2d1cd93","seq":12604,"text":"Testing Chat Bot .."}
}
]
}
]
}
Why is that and how can I fix that? Since json is a serialization mechanism I assumed it will be presented as string to the onWebhookEvent method.
Thanks for the help
If you want a request's body to be tied up to a parameter, use #RequestBody.
By the way, return a ResponseEntity object, as it is a wrapper to whatever you want to return, and you can specify additional information (for example, headers of the response)

Replying multiple times over web-socket without spring authentication

Note: see update at the bottom of the question for what I eventually concluded.
I need to send multiple responses to a request over the web socket that sent the request message, the first one quickly, and the others after the data is verified (somewhere between 10 and 60 seconds later, from multiple parallel threads).
I am having trouble getting the later responses to stop broadcasting over all open web sockets. How do I get them to only send to the initial web socket? Or should I use something besides Spring STOMP (because, to be honest, all I want is the message routing to various functions, I don't need or want the ability to broadcast to other web sockets, so I suspect I could write the message distributor myself, even though it is reinventing the wheel).
I am not using Spring Authentication (this is being retrofitted into legacy code).
On the initial return message, I can use #SendToUser, and even though we don't have a user, Spring only sends the return value to the websocket that sent the message. (see this question).
With the slower responses, though, I think I need to use SimpMessagingTemplate.convertAndSendToUser(user, destination, message), but I can't, because I have to pass in the user, and I can't figure out what user the #SendToUser used. I tried to follow the steps in this question, but didn't get it to work when not authenticated (principal.getName() returns null in this case).
I've simplified this considerably for the prototype, so don't worry about synchronizing threads or anything. I just want the web sockets to work correctly.
Here is my controller:
#Controller
public class TestSocketController
{
private SimpMessagingTemplate template;
#Autowired
public TestSocketController(SimpMessagingTemplate template)
{
this.template = template;
}
// This doesn't work because I need to pass something for the first parameter.
// If I just use convertAndSend, it broacasts the response to all browsers
void setResults(String ret)
{
template.convertAndSendToUser("", "/user/topic/testwsresponse", ret);
}
// this only sends "Testing Return" to the browser tab hooked to this websocket
#MessageMapping(value="/testws")
#SendToUser("/topic/testwsresponse")
public String handleTestWS(String msg) throws InterruptedException
{
(new Thread(new Later(this))).start();
return "Testing Return";
}
public class Later implements Runnable
{
TestSocketController Controller;
public Later(TestSocketController controller)
{
Controller = controller;
}
public void run()
{
try
{
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return");
}
catch (Exception e)
{
}
}
}
}
For the record, here is the browser side:
var client = null;
function sendMessage()
{
client.send('/app/testws', {}, 'Test');
}
// hooked to a button
function test()
{
if (client != null)
{
sendMessage();
return;
}
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect({}, function(frame)
{
client.subscribe('/user/topic/testwsresponse', function(message)
{
alert(message);
});
sendMessage();
});
});
And here is the config:
#Configuration
#EnableWebSocketMessageBroker
public class TestSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(MessageBrokerRegistry config)
{
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/queue", "/topic");
config.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
registry.addEndpoint("/sendws").withSockJS();
}
}
UPDATE: Due to the security issues involved with the possibility of information being sent over other websockets than the originating socket, I ended up recommending to my group that we do not use the Spring 4.0 implementation of STOMP over Web Sockets. I understand why the Spring team did it the way they did it, and it is more power then we needed, but the security restrictions on our project were severe enough, and the actual requirements were simple enough, that we decided to go a different way. That doesn't invalidate the answers below, so make your own decision based on your projects needs. At least we have hopefully all learned the limitations of the technology, for good or bad.
Why don't you use a separate topic for each client?
Client generates a session id.
var sessionId = Math.random().toString(36).substring(7);
Client subscribes to /topic/testwsresponse/{sessionId}, then sends a message to '/app/testws/{sessionId}'.
In your controller you use #MessageMapping(value="/testws/{sessionId}") and remove #SendToUser. You can use #DestinationVariable to access sessionId in your method.
The controller sends further responses to /topic/testwsresponse/{sessionId}.
Essentially Spring does a similar thing internally when you use user destinations. Since you don't use Spring Authentication you cannot rely on this mechanism but you can easily implement your own as I described above.
var client = null;
var sessionId = Math.random().toString(36).substring(7);
function sendMessage()
{
client.send('/app/testws/' + sessionId, {}, 'Test');
}
// hooked to a button
function test()
{
if (client != null)
{
sendMessage();
return;
}
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect({}, function(frame)
{
client.subscribe('/topic/testwsresponse/' + sessionId, function(message)
{
alert(message);
});
// Need to wait until subscription is complete
setTimeout(sendMessage, 1000);
});
});
Controller:
#Controller
public class TestSocketController
{
private SimpMessagingTemplate template;
#Autowired
public TestSocketController(SimpMessagingTemplate template)
{
this.template = template;
}
void setResults(String ret, String sessionId)
{
template.convertAndSend("/topic/testwsresponse/" + sessionId, ret);
}
#MessageMapping(value="/testws/{sessionId}")
public void handleTestWS(#DestinationVariable String sessionId, #Payload String msg) throws InterruptedException
{
(new Thread(new Later(this, sessionId))).start();
setResults("Testing Return", sessionId);
}
public class Later implements Runnable
{
TestSocketController Controller;
String sessionId;
public Later(TestSocketController controller, String sessionId)
{
Controller = controller;
this.sessionId = sessionId;
}
public void run()
{
try
{
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return", sessionId);
}
catch (Exception e)
{
}
}
}
}
Just tested it, works as expected.
This is not full answer. Just general consideration and suggestion.
You cannot do different stuff or type of connection via the same socket. Why not have different sockets for different work? Some with authentication and some without. Some for quick task and some for long execution.

Categories