[okhttp-gson] Add support for OAuth access token retry (#1058)

* Add support for access token retry in okhttp-gson lib

* Update expected number of generated files in test

* Update samples

* Update security samples

* Fix default user-agent and update samples
This commit is contained in:
Kiran-Sivakumar
2018-09-20 04:29:59 -07:00
committed by William Cheng
parent 1b2f3fbfb6
commit 0e045bee1b
29 changed files with 1166 additions and 73 deletions

View File

@@ -23,6 +23,9 @@ import org.threeten.bp.LocalDate;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
@@ -49,6 +52,7 @@ import org.openapitools.client.auth.Authentication;
import org.openapitools.client.auth.HttpBasicAuth;
import org.openapitools.client.auth.ApiKeyAuth;
import org.openapitools.client.auth.OAuth;
import org.openapitools.client.auth.RetryingOAuth;
public class ApiClient {
@@ -77,6 +81,39 @@ public class ApiClient {
* Constructor for ApiClient
*/
public ApiClient() {
init();
// Setup authentications (key: authentication name, value: authentication).
authentications.put("api_key", new ApiKeyAuth("header", "api_key"));
authentications.put("api_key_query", new ApiKeyAuth("query", "api_key_query"));
authentications.put("http_basic_test", new HttpBasicAuth());
authentications.put("petstore_auth", new OAuth());
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
/*
* Constructor for ApiClient to support access token retry on 401/403
*/
public ApiClient(
String clientId,
String clientSecret,
Map<String, String> parameters
) {
init();
RetryingOAuth retryingOAuth = new RetryingOAuth("", clientId, GrantType.valueOf("implicit"), clientSecret, parameters);
authentications.put(
"petstore_auth",
retryingOAuth
);
httpClient.interceptors().add(retryingOAuth);
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
private void init() {
httpClient = new OkHttpClient();
@@ -87,14 +124,7 @@ public class ApiClient {
// Set default User-Agent.
setUserAgent("OpenAPI-Generator/1.0.0/java");
// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();
authentications.put("api_key", new ApiKeyAuth("header", "api_key"));
authentications.put("api_key_query", new ApiKeyAuth("query", "api_key_query"));
authentications.put("http_basic_test", new HttpBasicAuth());
authentications.put("petstore_auth", new OAuth());
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
/**
@@ -484,6 +514,20 @@ public class ApiClient {
return this;
}
/**
* Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one)
* @return Token request builder
*/
public TokenRequestBuilder getTokenEndPoint() {
for (Authentication apiAuth : authentications.values()) {
if (apiAuth instanceof RetryingOAuth) {
RetryingOAuth retryingOAuth = (RetryingOAuth) apiAuth;
return retryingOAuth.getTokenRequestBuilder();
}
}
return null;
}
/**
* Format the given parameter object into string.
*

View File

@@ -0,0 +1,68 @@
package org.openapitools.client.auth;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import org.apache.oltu.oauth2.client.HttpClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthClientResponse;
import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
public class OAuthOkHttpClient implements HttpClient {
private OkHttpClient client;
public OAuthOkHttpClient() {
this.client = new OkHttpClient();
}
public OAuthOkHttpClient(OkHttpClient client) {
this.client = client;
}
@Override
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
MediaType mediaType = MediaType.parse("application/json");
Request.Builder requestBuilder = new Request.Builder().url(request.getLocationUri());
if(headers != null) {
for (Entry<String, String> entry : headers.entrySet()) {
if (entry.getKey().equalsIgnoreCase("Content-Type")) {
mediaType = MediaType.parse(entry.getValue());
} else {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}
}
RequestBody body = request.getBody() != null ? RequestBody.create(mediaType, request.getBody()) : null;
requestBuilder.method(requestMethod, body);
try {
Response response = client.newCall(requestBuilder.build()).execute();
return OAuthClientResponseFactory.createCustomResponse(
response.body().string(),
response.body().contentType().toString(),
response.code(),
responseClass);
} catch (IOException e) {
throw new OAuthSystemException(e);
}
}
@Override
public void shutdown() {
// Nothing to do here
}
}

View File

@@ -0,0 +1,147 @@
package org.openapitools.client.auth;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Map;
public class RetryingOAuth extends OAuth implements Interceptor {
private OAuthClient oAuthClient;
private TokenRequestBuilder tokenRequestBuilder;
public RetryingOAuth(OkHttpClient client, TokenRequestBuilder tokenRequestBuilder) {
this.oAuthClient = new OAuthClient(new OAuthOkHttpClient(client));
this.tokenRequestBuilder = tokenRequestBuilder;
}
public RetryingOAuth(TokenRequestBuilder tokenRequestBuilder) {
this(new OkHttpClient(), tokenRequestBuilder);
}
public RetryingOAuth(
String tokenUrl,
String clientId,
GrantType grantType,
String clientSecret,
Map<String, String> parameters
) {
this(OAuthClientRequest.tokenLocation(tokenUrl)
.setClientId(clientId)
.setGrantType(grantType)
.setClientSecret(clientSecret));
if (parameters != null) {
for (String paramName : parameters.keySet()) {
tokenRequestBuilder.setParameter(paramName, parameters.get(paramName));
}
}
}
@Override
public Response intercept(Chain chain) throws IOException {
return retryingIntercept(chain, true);
}
private Response retryingIntercept(Chain chain, boolean updateTokenAndRetryOnAuthorizationFailure) throws IOException {
Request request = chain.request();
// If the request already has an authorization (e.g. Basic auth), proceed with the request as is
if (request.header("Authorization") != null) {
return chain.proceed(request);
}
// Get the token if it has not yet been acquired
if (getAccessToken() == null) {
updateAccessToken(null);
}
OAuthClientRequest oAuthRequest;
if (getAccessToken() != null) {
// Build the request
Request.Builder requestBuilder = request.newBuilder();
String requestAccessToken = getAccessToken();
try {
oAuthRequest =
new OAuthBearerClientRequest(request.urlString()).
setAccessToken(requestAccessToken).
buildHeaderMessage();
} catch (OAuthSystemException e) {
throw new IOException(e);
}
Map<String, String> headers = oAuthRequest.getHeaders();
for (String headerName : headers.keySet()) {
requestBuilder.addHeader(headerName, headers.get(headerName));
}
requestBuilder.url(oAuthRequest.getLocationUri());
// Execute the request
Response response = chain.proceed(requestBuilder.build());
// 401/403 response codes most likely indicate an expired access token, unless it happens two times in a row
if (
response != null &&
( response.code() == HttpURLConnection.HTTP_UNAUTHORIZED ||
response.code() == HttpURLConnection.HTTP_FORBIDDEN ) &&
updateTokenAndRetryOnAuthorizationFailure
) {
try {
if (updateAccessToken(requestAccessToken)) {
response.body().close();
return retryingIntercept(chain, false);
}
} catch (Exception e) {
response.body().close();
throw e;
}
}
return response;
}
else {
return chain.proceed(chain.request());
}
}
/*
* Returns true if the access token has been updated
*/
public synchronized boolean updateAccessToken(String requestAccessToken) throws IOException {
if (getAccessToken() == null || getAccessToken().equals(requestAccessToken)) {
try {
OAuthJSONAccessTokenResponse accessTokenResponse =
oAuthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken());
return !getAccessToken().equals(requestAccessToken);
}
} catch (OAuthSystemException | OAuthProblemException e) {
throw new IOException(e);
}
}
return false;
}
public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}
public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
}