Refresh OAuth token with jersey 2 client - java

I'm currently migrating an application from jersey 1 to 2. In the old app, we used a ClientFilter for all jersey clients that automatically refreshed expired OAuth tokens like this:
#Override
public ClientResponse handle(ClientRequest cr) {
ClientResponse resp = getNext().handle(cr);
if (resp.getStatus() == Status.UNAUTHORIZED.getStatusCode()) {
// Try to refresh the token
boolean refreshed = refreshToken(oAuthInfo);
if (refreshed) {
resp = getNext().handle(cr);
}
}
return resp;
}
It might not haven been the most elegant way, but the benefit was that rest client users did not have to care about expired tokens themselves.
With the ContainerResponseFilter for jersey 2, this does not seem to be that simple anymore. The only option I currently see is to use the ClientRequestContext and try to re-create the original request using getClient, getHeaders etc... and then update the result in ContainerResponseContext. This however seems a bit clunky so I was wondering if there is any more convenient way to refresh an OAuth token without having to deal with this wherever a jersey client is used?

It looks like there is not more convenient way than intercepting the response with a client filter, refreshing the token if needed and trying to repeat the exact same request with the new token. In fact, this approach is also used by jersey own filter classes.
Sample code for repeating the original rest call from within a filter class can be found in jerseys HttpAuthenticationFilter:
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
Client client = request.getClient();
String method = request.getMethod();
MediaType mediaType = request.getMediaType();
URI lUri = request.getUri();
WebTarget resourceTarget = client.target(lUri);
Invocation.Builder builder = resourceTarget.request(mediaType);
MultivaluedMap<String, Object> newHeaders = new MultivaluedHashMap<String, Object>();
for (Map.Entry<String, List<Object>> entry : request.getHeaders().entrySet()) {
if (HttpHeaders.AUTHORIZATION.equals(entry.getKey())) {
continue;
}
newHeaders.put(entry.getKey(), entry.getValue());
}
newHeaders.add(HttpHeaders.AUTHORIZATION, newAuthorizationHeader);
builder.headers(newHeaders);
builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
Invocation invocation;
if (request.getEntity() == null) {
invocation = builder.build(method);
} else {
invocation = builder.build(method,
Entity.entity(request.getEntity(), request.getMediaType()));
}
Response nextResponse = invocation.invoke();
if (nextResponse.hasEntity()) {
response.setEntityStream(nextResponse.readEntity(InputStream.class));
}
MultivaluedMap<String, String> headers = response.getHeaders();
headers.clear();
headers.putAll(nextResponse.getStringHeaders());
response.setStatus(nextResponse.getStatus());
return response.getStatus() != Response.Status.UNAUTHORIZED.getStatusCode();
}
This code is used for example in DigestAuthenticator or BasicAuthenticator to repeat a request with provided credentials in case an Unauthorised response is received from server.

Related

Requesting Twitter api with OAuth 1.0

I'm trying to use Twitter's friends list api and was successful to do so without any parameters.
However whenever I add a parameter, I would get the error "Could not authenticate you." and I have no choice but to add a cursor parameter when the friend list is too long.
The fact that I get a list of users of friends when I call the api without any parameters makes me think that authenticating the request works properly.
I have tried to change the request url to https://api.twitter.com/1.1/friends/list.json?cursor=-1 which gives me the authentication error.
I tried using both https://api.twitter.com/1.1/friends/list.json and https://api.twitter.com/1.1/friends/list.json?cursor=-1 to make oauth_signature and they both failed me.
I tried using different parameters such as screen_name or user_id and they all will give me the same error.
I even tried to add cursor: -1 header like a POST request and that didn't work either.
Right now my code looks like this
public String getFriendList() {
String baseUrl = "https://api.twitter.com/1.1/friends/list.json";
// Creates a map with all necessary headers
Map<String, String> headers = createMap();
headers.put("oauth_token", <OAuth token of user>);
String signature = createSignature("GET", baseUrl, headers, <OAuth secret of user>);
// Add oauth_signature to header
headers.put("oauth_signature", signature);
String body = sendGetRequest(baseUrl, headers);
return body;
}
public String sendGetRequest(String baseUrl, Map<String, String> parameters) throws AuthException, IOException {
try (CloseableHttpClient client = CloseableHttpClientFactory.getHttpClient()) {
HttpGet httpGet = new HttpGet(baseUrl);
if (parameters != null) {
httpGet.setHeader("Authorization", createHeader(parameters));
}
CloseableHttpResponse response = client.execute(httpGet);
if (response.getStatusLine().getStatusCode() != 200) {
LOGGER.info("GET Request Failed : " + EntityUtils.toString(response.getEntity()));
throw new Exception();
}
String responseBody = EntityUtils.toString(response.getEntity());
return responseBody;
}
}
which is the working code.
Could anyone tell me where to add parameters and what I have missed to authenticate the request?
EDIT : Added code of sendGetRequest. Making the signature and adding the header was made by following the documentations from twitter

(Android+Retrofit 2) - Is there a drawback on the usage of Static Headers with Interceptors?

Let's say I have 5 API endpoints and 4 of them require the #Header "Authorization"; the other one doesn't.
Is there a drawback or anything of the sort when I use an Interceptor to insert the header for all API calls even though one of the endpoints does not require it? :)
Probably would be better use a more customizable approach that's retrofit2 provides - dynamic headers. A dynamic header is passed like a parameter to the method. The provided parameter value gets mapped by Retrofit before executing the request. Example:
#GET("/yourEndpoint")
Call<List<Obj>> getSomth(#Header("Your-Header") String yourHeader);
But if you only worrying about extra header passing, I don't see any drawback in your particular case.
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
Request request = chain.request();
if (!request.url().toString().contains("/tapi/login/login")) {
String cookie = SharedObj.getCookie();
builder.addHeader("Cookie", cookie);
}
return chain.proceed(builder.build());
}
This is my solution.All api need cookie except login api('/tapi/login/login') ,So I judge url to decide to wether to add cookie in request.
There is no problem using Interceptor with header for all API.
Set headers in interceptors and use to all API. If the API method is with or without auth it will be worked.
Like this - Create ones, use everywhere.
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder();
//Add headers here using requestbuilder.
String authToken =//Your Authtoken
if (authToken != null)
requestBuilder.header("Authorization", authToken);
requestBuilder.method(original.method(), original.body());
return chain.proceed(requestBuilder.build());
});
In Retrofit
Retrofit retrofit= new Retrofit.Builder().baseUrl("baseUrl").client(httpClient.build()).build();

Restlet client use authentication key

Using restlet JEE 2.3.2.
I have a client id and secret to interact with the server restful API. Submitting that info gets me back an authorization key that must be used for subsequent request. In curl, I can make queries using that key and can get data back:
curl -XGET "Authorization c79cec57-a52f-4e04-f3ca-55ea2a202114" "https://some/restful/endpoint"
How do I set my client resource to submit that authorization key? The online docs doesn't seem to cover this scenario.
if the scheme is not important, you can use a "Custom" scheme, (as it is mandatory in HTTP specification"). In order to avoid the warning "scheme is not supported by restlet engine", just register one, as follow:
You can achieve what you need using a "custom" scheme, as follow.
// Declare a custom Authenticator helper, if it is not standard
Engine.getInstance().getRegisteredAuthenticators().add(new AuthenticatorHelper(ChallengeScheme.CUSTOM, true, false) {});
// set up the reusable challenge response
ChallengeResponse cred = new ChallengeResponse(ChallengeScheme.CUSTOM);
cred.setRawValue("12344");
ClientResource cr = new ClientResource("http://localhost:8183/");
cr.setChallengeResponse(cred);
cr.get();
If you want an empty scheme, you can do as follow:
ChallengeResponse cred = new ChallengeResponse(new ChallengeScheme("",""));
cred.setRawValue("12345");
In this case, I think that you can use challenge response as described since such feature builds the Authorization header using format Authorization: Scheme ChallengeResponseContent:
ClientResource resource = new ClientResource(resouceURL);
String token = "myToken";
ChallengeResponse cr = new ChallengeResponse(
ChallengeScheme.HTTP_OAUTH_BEARER);
cr.setRawValue(token);
resource.setChallengeResponse(cr);
(...)
As a matter of fact, Restlet requires a challenge scheme that will be added before the token (or something else) within the value of the header Authorization. See extract from class AuthenticatorUtils#formatRequest:
public static String formatRequest(ChallengeRequest challenge,
Response response, Series<Header> httpHeaders) {
String result = null;
if (challenge == null) {
Context.getCurrentLogger().warning(
"No challenge response to format.");
} else if (challenge.getScheme() == null) {
Context.getCurrentLogger().warning(
"A challenge response must have a scheme defined.");
} else if (challenge.getScheme().getTechnicalName() == null) {
Context.getCurrentLogger().warning(
"A challenge scheme must have a technical name defined.");
} else {
ChallengeWriter cw = new ChallengeWriter();
cw.append(challenge.getScheme().getTechnicalName()).appendSpace();
int cwInitialLength = cw.getBuffer().length();
if (challenge.getRawValue() != null) {
cw.append(challenge.getRawValue());
} else {
(...)
In your case, I think that you need to build the header Authorization by yourself as described below:
ClientResource resource = new ClientResource(resouceURL);
String token = "myToken";
resource.getRequest().getHeaders().add("Authorization", token);
resource.get();
You can also implement a custom client resource for your needs in order to automatically apply the token:
public class ProtectedClientResource extends ClientResource {
private String token;
public ProtectedClientResource(String uri) {
super(uri);
}
#Override
public Response handleOutbound(Request request) {
if (token!=null) {
request.getHeaders().add("Authorization", token);
}
return super.handleOutbound(request);
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
Hope it helps you,
Thierry

Getting redirected URL in Apache HttpComponents

I'm using Apache HttpComponents to GET some web pages for some crawled URLs. Many of those URLs actually redirect to different URLs (e.g. because they have been processed with a URL shortener). Additionally to downloading the content, I would like to resolve the final URLs (i.e. the URL which provided the downloaded content), or even better, all URLs in the redirect chain.
I have been looking through the API docs, but got no clue, where I could hook. Any hints would be greatly appreciated.
One way is to turn off automatic redirect handling by setting the relevant parameter, and do it yourself by checking for 3xx responses, and manually extracting the redirect location from the responses "Location" header.
Here's a full demo of how to do it using Apache HttpComponents.
Important Details
You'll need to extend DefaultRedirectStrategy like so:
class SpyStrategy extends DefaultRedirectStrategy {
public final Deque<URI> history = new LinkedList<>();
public SpyStrategy(URI uri) {
history.push(uri);
}
#Override
public HttpUriRequest getRedirect(
HttpRequest request,
HttpResponse response,
HttpContext context) throws ProtocolException {
HttpUriRequest redirect = super.getRedirect(request, response, context);
history.push(redirect.getURI());
return redirect;
}
}
expand method sends a HEAD request which causes client to collect URIs in spy.history deque as it follows redirects automatically:
public static Deque<URI> expand(String uri) {
try {
HttpHead head = new HttpHead(uri);
SpyStrategy spy = new SpyStrategy(head.getURI());
DefaultHttpClient client = new DefaultHttpClient();
client.setRedirectStrategy(spy);
// FIXME: the following completely ignores HTTP errors:
client.execute(head);
return spy.history;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
You may want to set maximum number of redirects followed to something reasonable (instead of the default of 100) like so:
BasicHttpParams params = new BasicHttpParams();
params.setIntParameter(ClientPNames.MAX_REDIRECTS, 5);
DefaultHttpClient client = new DefaultHttpClient(params);

How do I add params and Cookie to http post java request?

I am trying to invoke a webservice method which takes 2 input parameters and also needs a cookie to authenticate.
PostMethod method = new PostMethod("webservice EP URL");
NameValuePair code = new NameValuePair("Code", "");
NameValuePair revision = new NameValuePair("Rev", "Latest");
NameValuePair targetUri = new NameValuePair("TARGET", "GetObject");
method.setRequestBody(new NameValuePair[] { code, revision,targetUri});
int statusNew = client.executeMethod(method);
I dont know how to achieve it. Above code is what i am doing currently.
Most probably you are dealing with RESTful web services (Just my guess as you are passing parameters as http form params). Here is how to pass cookies
method.setRequestHeader("Cookie", "special-cookie=value");
Here just change "special-cookie=value" to your specific cookie that you are trying to pass.
EDIT: Adding cookie to SOAP request:
The quickest way to do is as follows
(Assuming that the call object you are using is an instance of org.apache.axis.client.Call)
call.setProperty(
org.apache.axis.client.Call.SESSION_MAINTAIN_PROPERTY,
new Boolean(true));
call.setProperty(
org.apache.axis.transport.http.HTTPConstants.HEADER_COOKIE2,
"\r\nCookieName=" + "CookieValue");
Please check "Use a SOAPAction HTTP Header" topic on this link.
Using the SOAP Handler, we can pass the headers in the request and it will do the job.
GetObject_Service_Impl impl = new GetObject_Service_Impl();
// Get Iterator for all service ports
Iterator iter = impl.getPorts();
// Now create a new List of HandlerInfo objects - only one really.
// Our client handler
List handlerChain = new ArrayList();
handlerChain.add(new HandlerInfo(SoapHandler.class, null, null));
// Get Handler Registry
HandlerRegistry registry = impl.getHandlerRegistry();
// Register each port with the handler
while (iter.hasNext())
registry.setHandlerChain((QName) iter.next(), handlerChain);
And Write a new class say SoapHandler.java as below
public class SoapHandler extends GenericHandler {
HandlerInfo hi;
public void init(HandlerInfo info) {
hi = info;
}
public QName[] getHeaders() {
return hi.getHeaders();
}
public boolean handleResponse(MessageContext context) {
return true;
}
/**
* This method is use to add custom headers to existing SAOP request
*/
public boolean handleRequest(MessageContext context) {
System.out.println("response");
try {
SOAPMessageContext smc = (SOAPMessageContext) context;
SOAPMessage message = smc.getMessage();
MimeHeaders hd = message.getMimeHeaders();
hd.addHeader("Authorization", "Basic some credentials");
} catch (Exception e) {
throw new JAXRPCException(e);
}
return true;
}
}
And thats it.....its ready to go.

Categories