I have gone down many rabbit holes and cannot get this working. I am hoping someone can help me.
I am using Keycloak and my REST endpoints are successfully secured like this abbreviated example:
#Path("/api")
public class MyResource {
#Inject
SecurityIdentity securityIdentity;
#Inject
JsonWebToken jwt;
#GET
#Path("/mydata")
#RolesAllowed("user")
#NoCache
public Uni<Response> getMyData(Request request) {
// Get a claim from the Keycloak JWT
String mySpecialClaim = (String) jwt.claim("myCustomClaim").get();
// Do some work...
String resJson = "{result of work here}";
return Uni.createFrom().item(resJson)
.onItem()
.transform(item -> item != "" ? Response.ok(item) : Response.status(Response.Status.NO_CONTENT))
.onItem()
.transform(Response.ResponseBuilder::build);
}
}
The access token is supplied by the client app which manages the Keycloak authentication and which sends the API request with a Bearer token. Standard stuff, all working. :-)
Now, I want to do something similar with a WebSocket endpoint.
I am using the Quarkus Websockets sample as my guide and can get it all working without Authorization - ie making unsecured calls.
I am stuck trying to secure the WebSocket connection.
The closest I have come to finding a solution is this post in the Quarkus GitHub issues:
https://github.com/quarkusio/quarkus/issues/29919
I have coded that up as per the sample code in the post. Logging shows the reactive route and WebSocketSecurityConfigurator are both being called and the access_token from the JS WebSocket client is present and presumably being processed by the Quarkus default security processes, as it does for REST end points. All good.
The missing piece is how to code the onOpen() and onMessage() methods in my WebSocket endpoint so they are secure, reactive, and I can access the JWT to get the claims I need.
Can anyone elaborate on this code fragment from the Quarkus issue post mentioned above, please? I have added what I think I need as per the sample further below.
The fragment from the issue post:
#Authenticated
#ServerEndpoint(
value ="/ws",
configurator = WebSocketSecurityConfigurator.class
)
public class WebSocket {
#Inject
UserInfo userInfo;
// ...
}
My additions:
#Authenticated
#ServerEndpoint(
value ="/services/{clientid}",
configurator = WebSocketSecurityConfigurator.class
)
public class WebSocket {
#Inject
SecurityIdentity securityIdentity;
#Inject
JsonWebToken jwt;
#Inject
UserInfo userInfo;
#OnOpen
#RolesAllowed("user") // Is this possible here? Or do I use the JWT and test myself?
public void onOpen(Session session, #PathParam("clientid") String clientid) {
// Get a claim from the Keycloak JWT
String mySpecialClaim = (String) jwt.claim("myCustomClaim").get();
// Do some setup work...
// eg cache the session in a map, etc
}
#OnMessage
public void onMessage(String message, #PathParam("clientid") String clientid) {
// Get a claim from the Keycloak JWT
String myOtherSpecialClaim = (String) jwt.claim("myOtherCustomClaim").get();
// Do some work using the message...
String someMessage = "tell the world";
// Broadcast something ...
myBroadcastFunction(someMessage);
}
}
In the non-secure version, the onOpen() and onMessage() methods return void because, of course, unlike a REST endpoint, one broadcasts the result instead of returning it.
In this secured version that does not work. If I only have an onOpen() method, and code it like this:
#OnOpen
public void onOpen(Session session, #PathParam("clientid") String clientid) {
Log.info("websocket onOpen session=" + session.getId());
}
It throws:
Unhandled error in annotated endpoint org.flowt.orgserver.gateway.WebSocketGateway_Subclass#732f20f8
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException:
io.quarkus.runtime.BlockingOperationNotAllowedException: Blocking security check attempted in code running on the event loop.
Make the secured method return an async type, i.e. Uni, Multi or CompletionStage,
or use an authentication mechanism that sets the SecurityIdentity in a blocking manner prior to delegating the call
I would like not to block the event loop, so the first suggestion is the preferred one.
But how should I code that?
If I make the onOpen() return a Uni, how do I subscribe to it so it runs?
Can I still access the JWT to get the claims I need?
Do annotations like #RolesAllowed("user") still work in this context?
I wont waste space here with all my failed attempts. I am thinking I am not the first person to need to do this and there must be some kind of pattern to implement. The Quarkus docs are silent on this.
Can anyone tell me how to code the onOpen() and onMessage() methods using Quarkus so that the WebSocket endpoints are secured and the JWT is available inside those methods?
EDIT =======
To resolve the blocking exception, the Quarkus docs here say
To work around this you need to #Inject an instance
of io.quarkus.security.identity.CurrentIdentityAssociation,
and call the Uni<SecurityIdentity> getDeferredIdentity(); method.
You can then subscribe to the resulting Uni and will be
notified when authentication is complete and the identity
is available.
I cannot work out how to implement that instruction. Debugging into the Quarkus code I see that my access_token is being processed, the user is retrieved from Keycloak but the deferredIdentity is not being set. Therefore onOpen() never runs.
Clearly this is not what the docs mean me to do!
Here is my class:
package org.flowt.orgserver.gateway;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.websocket.Session;
import javax.websocket.OnOpen;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import io.quarkus.logging.Log;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.CurrentIdentityAssociation;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
#ApplicationScoped
#Authenticated
#ServerEndpoint(value = "/services/{clientid}", configurator = WebSocketSecurityConfigurator.class)
public class WebSocketGateway {
#Inject
SecurityIdentity securityIdentity;
#Inject
CurrentIdentityAssociation identities;
#OnOpen
public Uni<Void> onOpen(Session session, #PathParam("clientid") String clientid) {
// This never runs
Log.info("=========================== onOpen session=" + session.getId());
return identities.getDeferredIdentity()
.onItem()
.transformToUni(identity -> {
// Just to see if we reach here
Log.info("identity=" + identity.toString());
return Uni.createFrom().voidItem();
});
}
}
And just to reiterate: the REST endpoints in this same app for the same logged in user work perfectly.
Thanks,
Murray
Related
I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.
In my Spring boot controller, I am having a method that inserts some records to the backend, at the end of this operation, I notify the user via Javax email based on the response received from from previous operation.
Currently I get response from API after email method completes.
Is there any way I can return response to the client once my first operation is completed while email notification happens in background
I tried already implement Async annotation in sendemail method of mail service. But I cannot find any difference in response time and I still get the response only after the email is sent.
My pseudo code
Controller:
#Autowired
private EmailService emailService;
#PostMapping(value = "create", produces = "text/plain")
private insertRecord()
{
response = <Insert into DB>;
sendEmail(response);
}
private sendEmail(response)
{
//check if email should be sent and if yes
emailservice.send(response);
}
Email service:
#Service
public class EmailService {
#Async
public static void sendEmail(MailEvent mailEvent) throws IOException {//send
email}
}
Starter
#SpringBootApplication
#EnableAsync
public class Starter {...}
i didn't notice my async method was designed as static, removing static works for me.
I think Spring Async is the way to go to solve your problem. Did you you also enable the async functionality with #EnableAsync and create an Executor bean? See this guide for the full tutorial: https://spring.io/guides/gs/async-method/
I am having trouble grasping the idea of authorization in PlayFramework (version 2.5). My situation is I have a REST API method getUser and I want to restrict its access by performing authorization with a token that is coming in custom request header named "X-Authorization". Now my controller code looks like that:
package controllers;
import models.User;
import org.bson.types.ObjectId;
import play.mvc.*;
import org.json.simple.*;
import views.html.*;
public class ApiController extends Controller {
public Result getUser(String userId) {
User user = User.findById(new ObjectId(userId));
JSONObject userG = new JSONObject();
//Some code to append data to userG before return
return ok(userG.toJSONString());
}
}
The route URL is defined like this:
GET /api/user/:id controllers.ApiController.getUser(id)
Option 1 could be to check the Authorization token inside the method getUser and also check for other credentials but I want to restrict access before even it get calls getUser method. As in future I will be adding more method calls to this REST API. So I will be reusing the same authorization to those future REST APIs as well.
I found there is authorization available in Play Framework which I am not able to understand. I tried to implement Authorization by extending class Security.Authenticator and overriding methods getUserName and onUnauthorized like this:
package controllers;
import models.Site;
import models.User;
import org.json.simple.JSONObject;
import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security;
public class Secured extends Security.Authenticator {
#Override
public String getUsername(Context ctx) {
String auth_key = ctx.request().getHeader("X-Authorization");
Site site = Site.fineByAccessKey(auth_key);
if (site != null && auth_key.equals(site.access_key)) {
return auth_key;
} else {
return null;
}
}
#Override
public Result onUnauthorized(Context ctx) {
JSONObject errorAuth = new JSONObject();
errorAuth.put("status", "error");
errorAuth.put("msg", "You are not authorized to access the API");
return unauthorized(errorAuth.toJSONString());
}
}
Then I've appended the annotation to the getUser method with #Security.Authenticated(Secured.class). It works fine and returns unauthorized error. But now I am not sure if that is the preferred way. I feel this is not the right way to do it as the name of the override of function getUsername suggests that too. I am not checking for any username in session or cookie rather only the token present in the header of request.
Also I know there is a module named Deadbolt which is used for authorization but I read its documents and I am not able to integrate it. It was relatively complex integration for a beginner like me. I was confused about how to use it. I thought about using SubjectPresent controller authorization but still I was not able to implement it successfully.
In the end what do you guys suggest that should I use Security.Authenticator the way I have implemented? Or do you suggest that I go to my first option that is checking authorization inside getUser method? Or Anyone can tell me how to implement Deadbolt in my scenario?
You are mixing Authorization and Authentication.
Here is a good thread: Authentication versus Authorization
I like this answer:
Authentication = login + password (who you are)
Authorization = permissions (what you are allowed to do)
Authentication == Authorization (excluding anonymous user) if you allow doing something for all users that you know (i.e. Authenticated users)
The main goal of Deadbolt is Authorization (already Authenticated users). Your main goal is Authentication.
I would advise you to use Pac4J, it Authentication library not only for Play, and it has versions as for Java as for Scala. There is a good sample project: https://github.com/pac4j/play-pac4j-java-demo
I use this library myself in my projects and the task
As in future i will be adding more method calls to this REST api. So i
will be reusing the same authorization to those future REST apis as
well.
I solve as easy as just add the configuration in the 'application.conf`:
pac4j.security {
rules = [
{"/admin/.*" = {
authorizers = "ADMIN"
clients = "FormClient"
}}
]
}
Just do not forget to add Security filter. This feature present in the example project, so just clone and try.
Another example form the official page:
pac4j.security.rules = [
# Admin pages need a special authorizer and do not support login via Twitter.
{"/admin/.*" = {
authorizers = "admin"
clients = "FormClient"
}}
# Rules for the REST services. These don't specify a client and will return 401
# when not authenticated.
{"/restservices/.*" = {
authorizers = "_authenticated_"
}}
# The login page needs to be publicly accessible.
{"/login.html" = {
authorizers = "_anonymous_"
}}
# 'Catch all' rule to make sure the whole application stays secure.
{".*" = {
authorizers = "_authenticated_"
clients = "FormClient,TwitterClient"
}}
]
I have a Spring webservice #Controller class with a #MessageMapping annotated method as follows:
#MessageMapping("/trade")
public void executeTrade(MarketOrderRequest trade, Principal principal) {
trade.setUserID(principal.getName());
logger.debug("Trade: " + trade);
this.tradeService.executeTrade(trade);
}
I am sending a JSON string message built using the same MarketOrderRequest POJO as is accepted by the server method. With some Key:Value pairs which are set null (but are still present).
The WebSocketConfig class has configured the following endpoints:
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
When i try to send a message to this messagemapping using this code:
MarketOrderRequest request = new MarketOrderRequest();
//{set request variables..}
StompHeaders someHeaders = new StompHeaders();
someHeaders.putAll(sessionHeaders);
someHeaders.setDestination("/app/trade");
session.send(someHeaders, request);
With headers:
{Cookie=[JSESSIONID=8421F536B639126F84F12E655375D790; Path=/spring-websocket-portfolio/; HttpOnly], version=[1.2], heart-beat=[0,0], user-name=[fabrice], destination=[/app/trade]}
The server then prints that a method cannot be found for the request:
Searching methods to handle SEND /app/trade session=397da625042343b4bac1c913b6d8ec22 application/json;charset=UTF-8
payload={"uuid":null,"symbol":"EUR/USD","price":1.10182,"side":"1","qty":50000,"quoteID"...(truncated)
WebSocketAnnotationMethodMessageHandler[DEBUG] - No matching methods.
The server code is lifted from this project and altered slightly to suit my needs: link
I have added some role-based web socket security in an AbstractSecurityWebSocketMessageBrokerConfigurer implementation class as follows:
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/app/**").hasAnyRole("roleA", "roleB", "roleC")
//{some more subscribe dest matchers by role which are working}
}
would this possibly effect the WebSocketAnnotationMethodMessageHandler's attempts to map the request? It is pretty much the only change I have made to the config. My subscribe mappings are working perfectly.
To me it seems that there is a problem finding the method due to either the JSON or Principal parameters. I am sending the correct object type so is this possibly a problem with the User principal? Thanks
There was an error in my WebSocketConfig class.
The #componentscan annotation had the wrong package name. I updated the name to the correct value ( the name of my base package eg "com.my.project" ). Now during deployment in the logs, I can see the controller resources being mapped to the methods in my class.
Eg log output for one method:
Mapped "{[/order],messageType=[MESSAGE]}" onto public void com.my.project.web.PortfolioController.executeOrder(tradeObjects.OrderRequest,java.security.Principal)
I have a Jersey REST API and am using a ContainerRequestFilter to handle authorization. I'm also using #ManagedAsync on all endpoints so that my API can serve thousands of concurrent requests.
My authorization filter hits a remote service, but when the filter is run, Jersey hasn't yet added the current thread to it's internal ExecutorService, so I'm completely losing the async benefits.
Can I tell Jersey that I want this ContainerRequestFilter to be asynchronous?
#Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter
{
#Inject
private AuthorizationService authSvc;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
String authToken = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// HITS A REMOTE SERVER
AuthorizationResponse authResponse = authSvc.authorize(authToken);
if (!authResponse.isAuthorized())
{
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.entity("unauthorized!")
.build());
}
}
}
And here's an example resource:
#Path("/stuff")
#Produces(MediaType.APPLICATION_JSON)
public class StuffResource
{
#GET
#Path("/{id}")
#ManagedAsync
public void getById(#PathParam("id") long id, #Suspended final AsyncResponse ar)
{
Stuff s;
// HIT THE DATABASE FOR STUFF
ar.resume(s);
}
}
UPDATE Just heard back from the Jersey guys, and this is not possible as of 2.7. Only the resource method itself is invoked asynchronously, not filters. Any suggestions for proceeding still welcome.
This is not built in to Jersey as of 2.7.
#ManagedAsync is useless if you have any filters or interceptors that do any serious work (like hit a remote authorization service). They may add the ability to run filters asynchronously in the future, but for now you're on your own.
UPDATE - there are other ways...
After a long and perilous journey, I have found a very hacky solution that I'm using in the short term. Here is a rundown of what I tried and why it failed/worked.
Guice AOP - failed
I use Guice for DI (getting Guice injection to work with Jersey is a feat in itself!), so I figured I could use Guice AOP to get around the issue. Though Guice injection works, it is impossible to get Guice to create resource classes with Jersey 2, so Guice AOP cannot work with resource class methods. If you are trying desperately to get Guice to create resource classes with Jersey 2, don't waste your time because it will not work. This is a well-known problem.
HK2 AOP - RECOMMENDED SOLUTION
HK2 just recently released an AOP feature, see this question for details on how to get it working.
Monitoring - also worked
This is not for the faint of heart, and it is completely discouraged in the Jersey docs. You can register and ApplicationEventListener and override onRequest to return a RequestEventListener that listens for RESOURCE_METHOD_START and calls an authentication/authorization service. This event is triggered from the #ManagedAsync thread, which is the whole goal here. One caveat, the abortWith method is a no-op, so this won't work quite like a normal ContainerRequestFilter. Instead, you can throw an exception if auth fails instead, and register an ExceptionMapper to handle your exception. If someone is bold enough to give this a try, let me know and I'll post code.
I am not sure if this is what you were looking for but, have you looked into Spring's OncePerRequestFilter? I am currently using it for my authorization layer where each request goes through some filter that extends this OncePerRequestFilter depending on how my filters are mapped to the URLs. Here's a quick overview of how I am using it:
Authentication/Authorization of a resource in Dropwizard
I am not very clear on the async dispatch parts of these filters but I hope this link atleast sheds some light to what you are trying to achieve!
We use Spring security for authentication/authorization. I worked around the problem using a sub-resource locator with empty path as shown below:
#Path("/customers")
public class CustomerResource {
#Inject
private CustomerService customerService;
#Path("")
public CustomerSubResource delegate() {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return new CustomerSubResource(auth);
}
public class CustomerSubResource {
private final Authentication auth;
public CustomerSubResource(final Authentication auth) {
this.auth = auth;
}
#POST
#Path("")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#ManagedAsync
public void createCustomer(final Customer customer, #Suspended final AsyncResponse response) {
// Stash the Spring security context into the Jersey-managed thread
SecurityContextHolder.getContext().setAuthentication(this.auth);
// Invoke service method requiring pre-authorization
final Customer newCustomer = customerService.createCustomer(customer);
// Resume the response
response.resume(newCustomer);
}
}
}