I have a webservice client using CXF and Spring.
The client proxy is created as spring managed #Bean, and registered to the SpringBus.
The soap webservice requires a HTTP header named "Auth=[pass]", whereas pass must be a dynamic value, unique for each user that uses the client.
Here is the problem: I created an AbstractOutDatabindingInterceptor that puts the HTTP header before sending the XML to the webservice.
Which works fine for hardcoded pass. But I need to obtain the pass inside the interceptor dynamically. Therefore, I would somehow have to pass the currently logged in user INTO the interceptor itself. So that the interceptor then could fetch the proper authorization pass.
#Configuration
public class AppConfig {
#Autowired
SpringBus bus;
#PostConstruct
public void init() {
bus.getOutInterceptors().add(new AbstractOutDatabindingInterceptor(Phase.MARSHAL) {
#Override
public void handleMessage(Message message) throws Fault {
Map<String, List<String>> headers = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);
headers.put("Auth", Arrays.asList("dynamic pass per user"); //TODO how to get the current user dynamically?
}
});
}
#Bean
public WebservicePort getPort() {
WebservicePort port = new Webservice(bus);
return port;
}
}
#Service
public class WebserviceClient {
#Autowired
private WebservicePort port;
#Autowired
private AuthService authService;
public void run() {
//construct the xmlRequest
User user = authService.getCurrentUser();
port.send(xmlRequest); //invoke the auto-generated webservice stub
}
}
Of course I cannot change the auto generated method signature send(xml).
How can I get the current user into the OutInterceptor at time of sending dynamically, and just for this send request?
It must be threadsafe regarding the cxf client proxy, as multiple user might send a request simultaneously.
Related
I'm trying to implement custom auth flow in Keycloak. It should work similar to username&password flow (POST /openid-connect/token with params 'username'+'password'+'grant_type=password' -> response with access_token & refresh_token) but instead of username and password it will receive another fields (e.g. fieldA, filedB and hash)
I wrote an implementation of
org.keycloak.authentication.Authenticator
that does this auth, but I can't figure out what should I do next. How can I make keycloak validate user using this flow?
So If I understand you correctly: U have a custom implementation of the Authenticator interface, to register it in keyckoak you also need AuthenticatorFactory - implementation and add the path to it into the config file with the name: org.keycloak.authentication.AuthenticatorFactory. So you should have something like:
public class CustomAuthenticator implements Authenticator {
#Override
public void authenticate(AuthenticationFlowContext context) {
//todo make your auth validation check logic
String username = "admin";
UserModel user = context.getSession().users().getUserByUsername(username, context.getRealm());
context.setUser(user);
context.success(); // With context.success(), or failing with an error, you can manage your custom validation
}
}
public class CustomAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "custom-sso";
private static CustomAuthenticator SINGLETON = new CustomAuthenticator();
#Override
public String getDisplayType() {
return "custom sso";
}
#Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
#Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
#Override
public String getId() {
return PROVIDER_ID;
}
}
And also in file with path: src\main\resources\META-INF\services\org.keycloak.authentication.AuthenticatorFactory
need to add a path to the factory class.
After all these changes, you should be able to change your authentication flow from keyclaok admin page.
P.S. you cant change existed Browser flow, but you can copy it, change the copy and then bind the browser flow to your custom.
P.S.S.
I found almost the same question: Keycloak adding new authenticator
But with a better-described answer :-)
I need to send a message a client, after the creation of item. The item is created an ApiRest. Then I created my WebSocket with #ApplicationScope and I Injected in serviceREST with #Inject. The Problem is when the webSocket was initialized, in my serviceRest this webSocket's session still is null.
How I can use the web SOcket in my apirest?
#Path("citas")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class citaResource {
#Inject
com.softcase.citasmanager.websocket.ws websocket;
#GET
#Path("cita")
#Produces("application/json")
public Response cita() {
websocket.onMessage("Your Item was created");//Session of webSocket is null
return Response.ok("ItemCreated", MediaType.APPLICATION_JSON).build();
}
}
#ApplicationScope
#ServerEndpoint("/item")
public class ws{
private Session session;
#OnOpen
public void open(Session session) {
this.session = session;
}
#OnMessage
public void onMessage(String message) {
this.session.getBasicRemote().sendText(message);
}
A little context
Instances: there is a unique Session instance per client-server pair i.e. one instance of Session is created for a each client which connects to the WebSocket server endpoint. In short, the number of unique Session instances is equal to number of connected clients
Source: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html
For more details: https://tyrus-project.github.io/documentation/1.13.1/index/lifecycle.html
A suggestion is to use a static variables like
// #ApplicationScope
#ServerEndpoint("/item")
public class ws{
// something like
private static final Set<javax.websocket.Session> ALL_SESSIONS = new HashSet<>();
// ...
}
An example can be found here. It's an option but I do not think it solves your injection issue.
Another option is to leverage the javax.websocket.Session#getOpenedSessions() method such as this chat example. But once again, it does not solve the injection issue.
Your example
You are using both websocket and REST. As I understand, the flow is:
User A, B, C are connected
User A submits a request to citas/cita and receives the REST response
At the same time, A, B, C receive a websocket notification
So, as you wrote, on one hand, you have
#Path("citas")
// ...
public class CitaResource{
// ...
}
and
// #ApplicationScope -> commented as irrelevant in your situation
#ServerEndpoint("/item")
public class ws{
// ...
}
In the example, there is one instance of CitaResource when user A made the request and three instances of ws as A, B, C are connected. However, you were right about the injection: you need to have something injected in CitaResource but you need a bean that is always available and as you noticed, websocket instances are not a good option and which session the container must inject?
A websocket sessions handler
The solution is to use an application scoped bean to handle all the existing sessions. I got it from Oracle tutorial. It goes like this:
// com.softcase.citasmanager.websocket.SessionHandler
#ApplicatedScoped
#Named // optional
public class MySessionHandler{
private final Set<Session> ALL_SESSIONS;
// or use a map if you need to identify the
// the session by a key. This example uses Set
// private final Map<String, Session> ALL_SESSIONS;
public MySessionHandler(){
ALL_SESSIONS = new HashSet<>();
}
// manage sessions
public void addSession(Session session){
this.ALL_SESSIONS.add(session);
}
public void removeSession(Session session){
this.ALL_SESSIONS.remove(session);
}
// send messages to all instances:
public void sendMessage(String message){
this.ALL_SESSIONS.stream()
// optional
.filter(s -> s.isOpen())
// or whatever method you want to send a message
.forEach( s -> s.getBasicRemote().sendText(message);
}
// or if you want to target a specific session
// hence my questions in comments
public void sendMessage(String message, String target){
this.ALL_SESSIONS..stream()
// identity the target session
.filter(s -> s.equals(target))
// optional
.filter(s -> s.isOpen())
.forEach( s -> s.getBasicRemote().sendText(message);
}
}
Note:
I optionally check that the stored session is still opened. isOpen() is not mandatory but it might avoid some errors
Think the session handler as the "captain": it knows everything about the websocket sessions whereas the sessions themselves do not know about each other.
However, you need to adapt your endpoint to make the session handler efficient:
// com.softcase.citasmanager.websocket.WsCita
#ServerEndpoint
public class WsCita{
// there is no need to declare the session as attribute
// private Session session;
// ApplicatedScoped so always defined
#Inject
private MySessionHandler handler;
#OnOpen
public void open(Session session){
handler.addSession(session); // "Aye cap'tain, reporting from duty!"
// your stuff
}
#OnClose
public void close(Session session, CloseReason closeReason){
handler.removeSession(session); // "Cya cap'tain, that's all for today!"
// your stuff
}
// your OnMessage and other stuff here
}
Now we have set our websocket architecture, what now?
You have one instance of WsCita per client. At any time, there might be zero, one or more instances.
MySessionHandler knows this information and is #ApplicatedScoped so it is safe to inject it
The REST endpoint then changes to:
#Path("citas")
// ...
public class citaResource {
#Inject
com.softcase.citasmanager.websocket.SessionHandler handler;
#GET
// ...
public Response cita() {
// REST processing
// ...
// Websocket processing:
// - Handler is always here for you
// - Handler knows which websocket sessions to send the message to.
// The RestController is not aware of the recipients
handler.sendMessage("Your Item was created");
}
}
Please note that I put the websocket processing after the REST processing as you may not always send the message (e.g. creation or whatever exception).
Misc
Unrelated to your questions but I have some comments about your code:
Classes name are CamelCase starting with capitalized letter per Oracle recommendation
Avoid generic name for your classes such as Ws. I renamed it WsCita for the example
i am trying to implement server side events.
I have very simple resource exposed by a RESTful web service with Jersey/Grizzly. I try to broadcast the events with the SseBroadcaster. An event is created, whenever a data item comes in and is added to an internal list. A client should open a connection to the URL /events to receive the events.
#Path("sensordataelements")
public class SensorDataResource {
private SseBroadcaster broadcaster = new SseBroadcaster();
#GET
#Path("events")
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents() {
final EventOutput eventOutput = new EventOutput();
broadcaster.add(eventOutput);
return eventOutput;
}
#POST
#Path("/addraw")
#Produces(MediaType.APPLICATION_JSON)
public Response addRawSensorData(String elementBody) {
... data processing stuff ...
cList.add(
new SensorDataElement.SensorDataElementBuilder().id()
.sensorReading(tmpValue)
.build()
);
OutboundEvent evt = new OutboundEvent.Builder()
.data(Float.class, Float.valueOf(tmpValue))
.build();
broadcaster.broadcast(evt);
return Response.status(201).build();
}
...
I tried to connect with
curl -v http://localhost:8080/sensordataapp/sensordataelements/events
The connection is fine, but i do not get any events. I looked at some examples, but got the impression that this should work. What did i miss?
Thanks!
By default, a new instance of the resource class is created for each request. This means that a new broadcaster is created for each request, which isn't what you want. If you want to make the resource class a Singleton, you can simply annotate the class with #Singleton
#Singleton
#Path("sensordataelements")
public class SensorDataResource {
...
}
Now, only one instance of the resource class will be created for the entire application, and it will be shared for all requests.
The other option, is if you inject the broadcaster, instead of instantiating it yourself, you can inject it as a Singleton. Whether or not the resource class is a singleton or not, it will still get injected the same broadcaster instance. To do that, you can do something like the following in your ResourceConfig subclass
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new AbstractBinder() {
#Override
public void configure() {
bind(new SseBroadcaster()).to(SseBroadcaster.class);
}
});
}
}
Then in your resource class, just inject it
#Path("sensordataelements")
public class SensorDataResource {
#Inject
private SseBroadcaster broadcaster;
See also:
Dependency injection with Jersey 2.0
I am working on a Spring-MVC application and thanks to users on SO, we already have a working Cometd chat functionality. Another functionality we have in the application is notifications, but we would like to integrate Real-time notifications as soon as they happen, kinda like what Facebook has.
Basically the idea is, whenever a new notification is created, it will be saved in the database, and its information from the backend has to be passed to the notifications for logged in users on unique channel for each user.
I would like to know if this approach will work, as it will take me some doing to route notifications to the chat class. Please note, I don't have an interface for the ChatServiceImpl class too. Is that okay? Enough talking, here's code :
ChatServiceImpl :
#Named
#Singleton
#Service
public class ChatServiceImpl {
#Inject
private BayeuxServer bayeux;
#Session
private ServerSession serverSession;
public void sendNotification(Notification notification,int id
// And then I send notification here like below, by extracting information from the notification object.
ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/notification/" + id).getReference();
serverChannel.setPersistent(true);
serverChannel.publish(serverSession, output);
}
}
The above class has no interface, so I was planning to use the method as follows :
#Service
#Transactional
public class GroupCanvasServiceImpl implements GroupCanvasService{
private ChatServiceImpl chatService;
public void someMethod(){
chatService.sendNotification(notification, id);
}
}
BayeuxInitializer :
#Component
public class BayeuxInitializer implements DestructionAwareBeanPostProcessor, ServletContextAware
{
private BayeuxServer bayeuxServer;
private ServerAnnotationProcessor processor;
#Inject
private void setBayeuxServer(BayeuxServer bayeuxServer)
{
this.bayeuxServer = bayeuxServer;
}
#PostConstruct
private void init()
{
this.processor = new ServerAnnotationProcessor(bayeuxServer);
}
#PreDestroy
private void destroy()
{
System.out.println("Bayeux in PreDestroy");
}
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException
{
processor.processDependencies(bean);
processor.processConfigurations(bean);
processor.processCallbacks(bean);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException
{
return bean;
}
public void postProcessBeforeDestruction(Object bean, String name) throws BeansException
{
processor.deprocessCallbacks(bean);
}
#Bean(initMethod = "start", destroyMethod = "stop")
public BayeuxServer bayeuxServer()
{
return new BayeuxServerImpl();
}
public void setServletContext(ServletContext servletContext)
{
servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer);
}
}
Kindly let me know if this approach is okay. Thanks a lot.
The #Listener annotation is meant for methods that handle messages received from remote clients.
If you only need to send server-to-client messages, you don't strictly need to annotate any method with #Listener: it is enough that you retrieve the ServerChannel you want to publish to, and use it to publish the message.
In your particular case, it seems that you don't really need to broadcast a message on a channel for multiple subscribers, but you only need to send a message to a particular client, identified by the id parameter.
If that's the case, then it's probably better to just use peer-to-peer messaging in this way:
public void sendNotification(Notification notification, int id)
{
ServerSession remoteClient = retrieveSessionFromId(id);
remoteClient.deliver(serverSession, "/person/notification", notification);
}
This solution has the advantage to create a lot less channels (you don't need a channel per id).
Even better, you can replace the /person/notification channel (which is a broadcast channel) with a service channel such as /service/notification.
In this way, it is clear that the channel used to convey notifications is for peer-to-peer communication (because service channels cannot be used to broadcast messages).
The retrieveSessionFromId() method is something that you have to map upon user login, see for example the documentation about CometD authentication.
I am developing a REST API using RESTEasy with Guice and at the moment I am trying to incorporate basic authentication by use of an annotation similar to the #Auth found in Dropwizard. With
#Path("hello")
public class HelloResource {
#GET
#Produces("application/json")
public String hello(#Auth final Principal principal) {
return principal.getUsername();
}
}
the hello resource invocation should be intercepted by some code performing basic authentication using the credentials passed in the Authorization HTTP request header and on success injecting the principal into the method principal parameter. I would also like to be able to pass a list of allowed roles to the annotation, e.g. #Auth("admin").
I really need some advice in what direction to go to achieve this?
I think your best bet would be using an intermediate value within request scope. Assuming that you didn't put HelloResource in singleton scope, you can inject this intermediate value in some ContainerRequestFilter implementation and in your resource, and you can fill it inside this ContainerRequestFilter implementation with all authentication and authorization info you need.
It will look something like this:
// Authentication filter contains code which performs authentication
// and possibly authorization based on the request
#Provider
public class AuthFilter implements ContainerRequestFilter {
private final AuthInfo authInfo;
#Inject
AuthFilter(AuthInfo authInfo) {
this.authInfo = authInfo;
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// You can check request contents here and even abort the request completely
// Fill authInfo with the data you need
Principal principal = ...; // Ask some other service possibly
authInfo.setPrincipal(principal);
}
}
#Path("hello")
public class HelloResource {
private final AuthInfo authInfo;
#Inject
HelloResource(AuthInfo authInfo) {
this.authInfo = authInfo;
}
#GET
#Produces("application/json")
public String hello() {
// authInfo here will be pre-filled with the principal, assuming
// you didn't abort the request in the filter
return authInfo.getPrincipal().getUsername();
}
}
public class MainModule extends AbstractModule {
#Override
protected void configure() {
bind(AuthFilter.class);
bind(HelloResource.class);
bind(AuthInfo.class).in(RequestScoped.class);
}
}
And even if you did put the resource (or even the filter) in singleton scope for some reason, you can always inject Provider<AuthInfo> instead of AuthInfo.
Update
It seems that I was somewhat wrong in that the filter is by default not in singleton scope. In fact it seem to behave like singleton even though it is not bound as such. It is created upon JAX-RS container startup. Hence you will need to inject Provider<AuthInfo> into the filter. In fact, the container startup will fail if AuthInfo is injected into the filter directly while being bound to request scope. Resource (if not explicitly bound as singleton) will be OK with direct injection though.
I have uploaded working program to github.