Open danielborges93 opened 7 years ago
@danielborges93 After the Websocket handshake you no longer have access to the HttpSession object, to get the HttpSession you should provide a ServerEndpoint.Configurator and intercept the HttpSession object in the modifyHandshake method and pass that object to your Websocket Endpoint instance (it should have a HttpSession setter btw), here is a working example (I am using Dropwizard-Guicey as DI):
public class MintyWebsocketConfigurator<A extends Application> extends ServerEndpointConfig.Configurator {
private A dropwizardApp;
private HttpSession httpSession;
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
httpSession = (HttpSession) request.getHttpSession();
super.modifyHandshake(sec, request, response);
}
public MintyWebsocketConfigurator(A dropwizardApp) {
this.dropwizardApp = dropwizardApp;
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
final T websocketEndpoint = InjectorLookup.getInjector(dropwizardApp).get().getInstance(endpointClass);
if (websocketEndpoint instanceof HttpSessionAwareWebsocketResource) {
((HttpSessionAwareWebsocketResource) websocketEndpoint).setHttpSession(httpSession);
}
return websocketEndpoint;
}
}
I spent a day or two dealing with this and would like to share the best solution I could come up with. I used the answer by @georgerb as a basis, thanks. This solution doesn't depend upon a specific DI framework and avoids the race condition between modifyHandshake
and getEndpointInstance
present in the previously suggested solution (AFAICT the configurator instance is shared between request threads). In an ideal world we would somehow modify the session object to set the Principal
directly, but I couldn't find a way to do this. As such, the main source of ugliness in this solution is having to put the Principal
into the user properties map. I would be very grateful if someone had a solution to that
Any feedback/improvements are very welcome
A few things to note:
We implement a configurator that takes e.g. a Basic Auth authenticator, extracts the credentials/token from the upgrade request, verifies them, then adds the user to the session properties so they can be accessed in the Endpoint
class
public class AuthenticatedEndpointConfigurator
extends ServerEndpointConfig.Configurator {
public AuthenticatedEndpointConfigurator(
final SomeAuthenticatorImplementation authenticator
) {
this.authenticator = authenticator;
}
@Override
public void modifyHandshake(
final ServerEndpointConfig sec,
final HandshakeRequest request,
final HandshakeResponse response
) {
try {
final User user = authenticator.authenticate(
credentialsFromQueryString(
request.getQueryString()
)
).orElse(() ->
// handle authentication failure here
);
sec.getUserProperties().put(User.class.getName(), user);
super.modifyHandshake(sec, request, response);
} catch (final AuthenticationException e) {
// handle invalid/malformed credentials here
}
}
private final SomeAuthenticatorImplementation authenticator;
}
Example endpoint that extracts the user and sets the member variable so it can be accessed throughout the lifetime of the socket connection
@ServerEndpoint("/some/endpoint")
public class SomeEndpoint {
@OnOpen
public void onOpen(final Session session) {
user = (User) session.getUserProperties().get(User.class.getName());
}
@OnClose
public void close(Session session) {
session.getAsyncRemote().sendText("Goodbye, " + user.getName());
}
// There is a unique instance of this class per session, but any thread can
// call the methods here so instance fields should be volatile
private volatile User user;
}
Then in our Application
class we have
@Override
public void initialize(
final Bootstrap<SomeConfiguration> bootstrap
) {
websocketBundle = new WebsocketBundle(
// just use a default configurator here
new ServerEndpointConfig.Configurator()
);
bootstrap.addBundle(websocketBundle);
}
@Override
public void run(
final SomeConfiguration configuration,
final Environment environment
) {
final ServerEndpointConfig sec =
ServerEndpointConfig.Builder
.create(SomeEndpoint.class, "/some/endpoint")
.configurator(
new AuthenticatedEndpointConfigurator(authenticator)
)
.build();
websocketBundle.addEndpoint(sec);
}
Hi!
It is possible use the default
io.dropwizard.auth
withdropwizard-websockets
? In our project we are using OAuth authenticantion in resources.I'm testing this class
with this simple node script:
and the Principal always is
null
.I want to associate each session ith the logged user.
Thanks!