OAuth in gRPC using Java - java

I'm trying to implement OAuth2 between my gRPC server and client applications using interceptors, with the following steps:
client app calls a server's gRPC method
server app responds with UNAUTHENTICATED status and a redirect-url in the headers
client obtains the redirect-url, uses it to access Authorization server, and finally gets an access_token
client app calls a server's gRPC method (this time, with access_token)
However, step #4 seems impossible in just one call since the transaction is already closed at step #2. Is there a way to do these 4 steps in just one gRPC service call?
Here is my ClientInterceptor class. I indicated the 4 steps in the code (see code comments).
public class OAuthClientInterceptor implements ClientInterceptor {
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new CheckedForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
#Override
public void checkedStart(Listener<RespT> responseListener, Metadata headers) {
if (redirectUrl != null) {
try {
//[Step #3] Obtain the access token
accessToken = obtainAccessToken(redirectUrl);
} catch (ConnectException e) {
throw new StatusRuntimeException(Status.UNAUTHENTICATED.withCause(e));
}
}
if (accessToken != null) {
headers.put(Key.of("Authorization",
Metadata.ASCII_STRING_MARSHALLER), "Bearer " + accessToken);
}
if (recursiveCall) {
//[Step #4] PROBLEM: still results to UNAUTHENTICATED
next.newCall(method, callOptions).start(responseListener, headers);
recursiveCall = false;
return;
}
OAuthResponseListener<RespT> oAuthRespListener = new OAuthResponseListener(responseListener);
oAuthRespListener.setUnauthenticatedListener(trailers->{
//[Step #2] Obtain the redirect-url
redirectUrl = trailers.get(Key.of("redirect-url", Metadata.ASCII_STRING_MARSHALLER));
recursiveCall = true;
//[Step #3 and 4] Invoke the retrieval of access token and the 2nd call to gRPC method
checkedStart(responseListener, headers);
});
//[Step #1] Call the gRPC method
delegate().start(oAuthRespListener, headers);
}
};
}
}

I have resolved this issue, but I was hoping to find some built-in authentication mechanism or multiple calls to a blockingStub function to support this oAuth flow, but couldn't find any.
So I resorted to calling blockingStub.invokeServerMethod() at least twice;
to know if it's not yet authenticated, and to be able to obtain redirect-url
to be able to call invokeServerMethod with access_token attached to headers.
Note that I have 30 server methods, and I have to implement the same steps to all of these method calls. To minimize code duplicates for each server method, I created a RetryUtil class which will be called for each server method. Here's what I did:
public class GrpcClient {
public SomeResponse callServerMethod() {
//invoke the method twice
return RetryUtil.retry(() -> blockingStub.invokeServerMethod(), 2);
}
}
public class RetryUtil {
public static <T> T retry(Supplier<T> supplier, int retryCount) {
StatusRuntimeException finalEx = null;
for (int i=0; i<retryCount; i++) {
try {
return supplier.get();
} catch (StatusRuntimeException e) {
if (e.getStatus() != Status.UNAUTHENTICATED) {
throw e;
}
finalEx = e;
}
}
throw finalEx;
}
}

Related

how to properly wrap method.invoke() (reflect) in a reactive java?

I'm trying to make a service in reactive java,
another service will send me requests.
I catch these requests and want to run methods according to the incoming URI
Here I receive a request from another service and run the desired method:
public Mono<Response> requestResponse(Request message, ByteBuf metadata) {
return controller.startMethod(message)
.onErrorResume(error -> Mono.just(buildResponse(error.getMessage(), message, Status.STATUS_INTERNAL_SERVER_ERROR)))
.switchIfEmpty(Mono.just(buildResponse("NULL", message, Status.STATUS_BAD_REQUEST)))
.doOnNext(logResponse());
}
This is the launch itself, the method is located by the annotation and the http method
public Mono<Response> startMethod(Request message) {
for (Method method : clazz.getDeclaredMethods()) {
if (isNecessaryMethod(method, message)) {
try {
return (Mono<Response>) method.invoke(context.getBean(clazz), initParameters(method, message));
} catch (Throwable e) {
return Mono.error(e);
}
}
}
return Mono.error(new PathNotFound());
}
Subsequently, the method should run, this is an example:
#Request(url = "/save-token", method = POST)
public Mono<Response> saveToken(String token) {
return Mono.empty();
}
I would like to know how to properly process method.invoke(...) so that everything works reactively and correctly

How to refresh ACCESS-TOKEN in Retrofit2/rxJava

I make a request (any, authorization, registration, etc.) and only then I find out that I need to update the ACCESS-TOKEN, that is, I get the error 401.
Here is the authorization request:
BaseApplication.getApiClient()
.signIn(accessToken, body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)){
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
The API requests:
#POST("/api/login")
Single<UserProfile> getAccessToken(#Body Map<String, String> requestBody);
#POST("/api/abonent/login")
Single<UserProfile> signIn(#Header("X-ACCESS-TOKEN") String accessToken,
#Body Map<String, String> requestBody);
For example, the request for authorization is request 1, the request to receive TOKEN is query 2.
Question: How can I update TOKEN if I get an error in query 1 and after query 2 succeeds, back to do query 1?
I'm not sure how you receive the new token, since the return type of getAccessToken() is Single<UserProfile>. I suppose it should be Single<String> instead. Maybe this is not the case and you receive the token in a header or as a field of UserProfile. In either case, you can get an idea from the below solution and adjust it to your case.
The approach is that we create a new observable from your original one that uses a token store, which holds the most up-to-date token. We handle the 401 error using compose and onErrorResumeNext so that a token refresh request is made, the new token is saved to the token store, and the original request is retried with the new token this time.
For a more detailed explanation, see the comments in the code below:
public void signIn(final Map<String, String> body) {
Single
// Wrap the original request with a "defer" so that the access token is
// evaluated each time it is called. This is important because the refreshed
// access token should be used the second time around.
.defer(new Callable<SingleSource<UserProfile>>() {
#Override
public SingleSource<UserProfile> call() throws Exception {
return BaseApplication.getApiClient()
.signIn(accessTokenStore.getAccessToken(), body);
}
})
// Compose it with a transformer that refreshes the token in the token store and
// retries the original request, this time with the refreshed token.
.compose(retryOnNotAuthorized(body))
// The code remains the same from here.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)) {
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
}
#NonNull
private SingleTransformer<UserProfile, UserProfile> retryOnNotAuthorized(final Map<String, String> body) {
return new SingleTransformer<UserProfile, UserProfile>() {
#Override
public SingleSource<UserProfile> apply(final Single<UserProfile> upstream) {
// We use onErrorResumeNext to continue our Single stream with the token refresh
// and the retrial of the request.
return upstream.onErrorResumeNext(new Function<Throwable, SingleSource<? extends UserProfile>>() {
#Override
public SingleSource<UserProfile> apply(Throwable throwable) throws Exception {
if (throwable instanceof HttpException
&& ((HttpException) throwable).code() == 401) {
return BaseApplication.getApiClient().getAccessToken(body)
// I always use doOnSuccess() for non-Rx side effects, such as caching the token.
// I think it's clearer than doing the caching in a map() or flatMap().
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String accessToken) throws Exception {
// Save the access token to the store for later use.
accessTokenStore.storeAccessToken(accessToken);
}
})
// We don't need the result of getAccessToken() any more, so I
// think it's cleaner to convert the stream to a Completable.
.toCompletable()
// After the token is refreshed and stored, the original request
// should be repeated.
.andThen(upstream);
}
// If the error was not 401, pass through the original error
return Single.error(throwable);
}
});
}
};
}
Update: The token store is just a regular interface with a get and a store method. You should implement it either as a POJO (storing the token in a field) or you could store the token in a shared preference so that the token survives app restarts.

How to continue request processing after sending response from filter in Jersey?

I have a situation where I need to return an "accepted" response for every request received and publish the actual response later to a separate endpoint outside the service.
To implement the 'accepted' Response I implemented a filter.
public class AcknowledgementFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.abortWith(Response.accepted().build());
// call Resource method in new Thread() . <------ ?
}
}
Implementation of service endpoints:
#Path("/vendor")
public class VendorHandler {
#POST
public void addVendor(VendorRequest addVendorRequest)){
vendor = new Vendor();
Client.publish(vendor); // publish request to an endpoint
return null;
}
How do I call the addVendor of VendorHandler(or any method depends on request) from the acknowledgement filter?
Is there any other way to implement an accepted response for every request then process the request separately?
You can use AsyncResponse,
#GET
#ManagedAsync
#Produces(MediaType.APPLICATION_JSON)
public void getLives(#Suspended final AsyncResponse asyncResponse,
#DefaultValue("0") #QueryParam("newestid") final int newestId,
#QueryParam("oldestid") final Integer oldestId) {
asyncResponse.setTimeoutHandler(asyncResponse1 -> {
logger.info("reached timeout");
asyncResponse1.resume(Response.ok().build());
});
asyncResponse.setTimeout(5, TimeUnit.MINUTES);
try {
List<Life> lives = oldestId == null ?
Lifes.getLastLives(newestId) : Lifes.getOlderLives(oldestId);
if (lives.size() > 0) {
final GenericEntity<List<Life>> entity = new GenericEntity<List<Life>>(lives) {
};
asyncResponse.resume(entity);
} else LifeProvider.suspend(asyncResponse);
} catch (SQLException e) {
logger.error(e, e);
asyncResponse.resume(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
}
}
Check this Link for more details.

Java Websocket / MessageHandler return to global scope?

I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}

Retrofit connection failure returns RetrofitError.response as null

Using Retrofit 1.6.0, OkHTTP 2.0.0, and OkHTTP-UrlConnection 2.0.0.
I am doing a POST to a service using Retrofit to a URL that does not exist. The failure callback is called, as expected. However, the RetrofitError parameter does not have a response. I would really like to grab the HTTP status code that was returned by using
error.getResponse().getStatus()
but since getResponse() returns null, I can't.
Why is getResponse() null and how can I get the status?
Thanks.
Also, the error I am receiving is UnknownHostException, as expected. Repeat: I am expecting this error. I want to know how to get the HTTP status code or why error.getResponse() returns null.
Edit: Here's some code:
RestAdapterBuilderClass.java
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://badURL.DoesntMatter/");
.setRequestInterceptor(sRequestInterceptor)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
sService = restAdapter.create(ServiceInterface.class);
ServiceInterface.java
#POST("/Login")
void login(#Body JsonObject body, Callback<String> callback);
CallbackClass.java
#Override
public void failure(RetrofitError error) {
if (error.getResponse() == null) {
// error.getResponse() is null when I need to get the status code
// from it.
return;
}
}
When you get an UnknownHostException it means, that you were not able to establish a connection to the server. You cannot, in fact, expect a HTTP status in that case.
Naturally you only get a Http response (and with that a status) when you can connect to a server.
Even when you get a 404 status code, you made a connection to the server. That is not the same as a UnknownHostException.
The getResponse() can return null if you didn't get a response.
RetrofitError has a method called isNetworkError() that you can use to detect a failed request due to network problems. I usually add a small helper method like this:
public int getStatusCode(RetrofitError error) {
if (error.isNetworkError()) {
return 503; // Use another code if you'd prefer
}
return error.getResponse().getStatus();
}
and then use that result to handle any additional failure logic (logout on 401, display error message on 500, etc).
Just to expand on #lyio's answer, I found from Fabric logging that getKind() sometimes returns UNEXPECTED and then if you parse the message you get timeouts and connection issues so I wrote the utility class below.
public class NetworkUtils {
// Compiled from Fabric events
private static final List<String> networkErrorStrings = new ArrayList<>(Arrays.asList(
"Unable to resolve host",
"Connection closed by peer",
"Failed to connect",
"timeout",
"Connection timed out"));
public static boolean isNetworkError(#Nullable RetrofitError retrofitError) {
if (retrofitError != null) {
if (retrofitError.getKind() != RetrofitError.Kind.NETWORK) {
for (String error : networkErrorStrings) {
if (retrofitError.getMessage().contains(error)) {
return true;
}
}
} else {
return true;
}
}
return false;
}
}
I am using Retrofit 2. When endpoint url end with "/" as in your case and again in your interface it starts with "/" [#POST("/Login")] causes this problem. Remove the "/" from .setEndpoint() method

Categories