[JAVA][Feign] Replace Apache oltu with scribejava (#8318)

* - Replace apache oltu with scribejava
- Implement the following authentication methods
  - ApiKey header
  - HTTP basic authentication
  - Oauth client credentials flow
  - Oauth Implicit flow
  - Oauth Pasword (deprecated)

* Create class hierarchy for Oauth flows implementation

* Add instructions of how to use the ApiClient to Readme.md

* Update samples

* Remove support for java 6 and 7

* Remove java 6 and 7 support from gradle

* Format pom.xml

* Remove empty line

* Update samples

* Remove oltu dependency from build.gradle and build.sbt.
Replace oltu with ScribeJava

Update samples

* Update samples

* Update samples
This commit is contained in:
Hugo Alves
2021-01-19 04:41:25 +00:00
committed by GitHub
parent 6e4c1307a7
commit ede2a2316c
43 changed files with 1208 additions and 1211 deletions

View File

@@ -2,9 +2,8 @@ package org.openapitools.client;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.threeten.bp.*;
@@ -24,6 +23,8 @@ import org.openapitools.client.auth.OAuth.AccessTokenListener;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public class ApiClient {
private static final Logger log = Logger.getLogger(ApiClient.class.getName());
public interface Api {}
protected ObjectMapper objectMapper;
@@ -43,6 +44,7 @@ public class ApiClient {
public ApiClient(String[] authNames) {
this();
for(String authName : authNames) {
log.log(Level.FINE, "Creating authentication {0}", authName);
RequestInterceptor auth;
if ("api_key".equals(authName)) {
auth = new ApiKeyAuth("header", "api_key");
@@ -51,7 +53,7 @@ public class ApiClient {
} else if ("http_basic_test".equals(authName)) {
auth = new HttpBasicAuth();
} else if ("petstore_auth".equals(authName)) {
auth = new OAuth(OAuthFlow.implicit, "http://petstore.swagger.io/api/oauth/dialog", "", "write:pets, read:pets");
auth = buildOauthRequestInterceptor(OAuthFlow.implicit, "http://petstore.swagger.io/api/oauth/dialog", "", "write:pets, read:pets");
} else {
throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
}
@@ -77,34 +79,6 @@ public class ApiClient {
this.setApiKey(apiKey);
}
/**
* Helper constructor for single basic auth or password oauth2
* @param authName
* @param username
* @param password
*/
public ApiClient(String authName, String username, String password) {
this(authName);
this.setCredentials(username, password);
}
/**
* Helper constructor for single password oauth2
* @param authName
* @param clientId
* @param secret
* @param username
* @param password
*/
public ApiClient(String authName, String clientId, String secret, String username, String password) {
this(authName);
this.getTokenEndPoint()
.setClientId(clientId)
.setClientSecret(secret)
.setUsername(username)
.setPassword(password);
}
public String getBasePath() {
return basePath;
}
@@ -147,10 +121,25 @@ public class ApiClient {
return objectMapper;
}
private RequestInterceptor buildOauthRequestInterceptor(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
switch (flow) {
case password:
return new OauthPasswordGrant(tokenUrl, scopes);
case application:
return new OauthClientCredentialsGrant(authorizationUrl, tokenUrl, scopes);
default:
throw new RuntimeException("Oauth flow \"" + flow + "\" is not implemented");
}
}
public ObjectMapper getObjectMapper(){
return objectMapper;
}
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* Creates a feign client for given API interface.
*
@@ -197,19 +186,13 @@ public class ApiClient {
return contentTypes[0];
}
/**
* Helper method to configure the bearer token.
* @param bearerToken the bearer token.
*/
public void setBearerToken(String bearerToken) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof HttpBearerAuth) {
((HttpBearerAuth) apiAuthorization).setBearerToken(bearerToken);
return;
}
}
throw new RuntimeException("No Bearer authentication configured!");
HttpBearerAuth apiAuthorization = getAuthorization(HttpBearerAuth.class);
apiAuthorization.setBearerToken(bearerToken);
}
/**
@@ -217,63 +200,38 @@ public class ApiClient {
* @param apiKey API key
*/
public void setApiKey(String apiKey) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof ApiKeyAuth) {
ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization;
keyAuth.setApiKey(apiKey);
return ;
}
}
throw new RuntimeException("No API key authentication configured!");
ApiKeyAuth apiAuthorization = getAuthorization(ApiKeyAuth.class);
apiAuthorization.setApiKey(apiKey);
}
/**
* Helper method to configure the username/password for basic auth or password OAuth
* Helper method to configure the username/password for basic auth
* @param username Username
* @param password Password
*/
public void setCredentials(String username, String password) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof HttpBasicAuth) {
HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization;
basicAuth.setCredentials(username, password);
return;
}
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
oauth.getTokenRequestBuilder().setUsername(username).setPassword(password);
return;
}
}
throw new RuntimeException("No Basic authentication or OAuth configured!");
HttpBasicAuth apiAuthorization = getAuthorization(HttpBasicAuth.class);
apiAuthorization.setCredentials(username, password);
}
/**
* Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one)
* @return Token request builder
* Helper method to configure the client credentials for Oauth
* @param username Username
* @param password Password
*/
public TokenRequestBuilder getTokenEndPoint() {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
return oauth.getTokenRequestBuilder();
}
}
return null;
public void setClientCredentials(String clientId, String clientSecret) {
OauthClientCredentialsGrant authorization = getAuthorization(OauthClientCredentialsGrant.class);
authorization.configure(clientId, clientSecret);
}
/**
* Helper method to configure authorization endpoint of the first oauth found in the apiAuthorizations (there should be only one)
* @return Authentication request builder
* Helper method to configure the username/password for Oauth password grant
* @param username Username
* @param password Password
*/
public AuthenticationRequestBuilder getAuthorizationEndPoint() {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
return oauth.getAuthenticationRequestBuilder();
}
}
return null;
public void setOauthPassword(String username, String password, String clientId, String clientSecret) {
OauthPasswordGrant apiAuthorization = getAuthorization(OauthPasswordGrant.class);
apiAuthorization.configure(username, password, clientId, clientSecret);
}
/**
@@ -281,14 +239,9 @@ public class ApiClient {
* @param accessToken Access Token
* @param expiresIn Validity period in seconds
*/
public void setAccessToken(String accessToken, Long expiresIn) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
oauth.setAccessToken(accessToken, expiresIn);
return;
}
}
public void setAccessToken(String accessToken, Integer expiresIn) {
OAuth apiAuthorization = getAuthorization(OAuth.class);
apiAuthorization.setAccessToken(accessToken, expiresIn);
}
/**
@@ -298,19 +251,7 @@ public class ApiClient {
* @param redirectURI Redirect URI
*/
public void configureAuthorizationFlow(String clientId, String clientSecret, String redirectURI) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
oauth.getTokenRequestBuilder()
.setClientId(clientId)
.setClientSecret(clientSecret)
.setRedirectURI(redirectURI);
oauth.getAuthenticationRequestBuilder()
.setClientId(clientId)
.setRedirectURI(redirectURI);
return;
}
}
throw new RuntimeException("Not implemented");
}
/**
@@ -318,13 +259,8 @@ public class ApiClient {
* @param accessTokenListener Acesss token listener
*/
public void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) {
if (apiAuthorization instanceof OAuth) {
OAuth oauth = (OAuth) apiAuthorization;
oauth.registerAccessTokenListener(accessTokenListener);
return;
}
}
OAuth apiAuthorization = getAuthorization(OAuth.class);
apiAuthorization.registerAccessTokenListener(accessTokenListener);
}
/**
@@ -349,4 +285,11 @@ public class ApiClient {
feignBuilder.requestInterceptor(authorization);
}
private <T extends RequestInterceptor> T getAuthorization(Class<T> type) {
return (T) apiAuthorizations.values()
.stream()
.filter(requestInterceptor -> type.isAssignableFrom(requestInterceptor.getClass()))
.findFirst()
.orElseThrow(() -> new RuntimeException("No Oauth authentication or OAuth configured!"));
}
}

View File

@@ -0,0 +1,47 @@
package org.openapitools.client.auth;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import com.github.scribejava.core.extractors.TokenExtractor;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public class DefaultApi20Impl extends DefaultApi20 {
private final String accessTokenEndpoint;
private final String authorizationBaseUrl;
protected DefaultApi20Impl(String authorizationBaseUrl, String accessTokenEndpoint) {
this.authorizationBaseUrl = authorizationBaseUrl;
this.accessTokenEndpoint = accessTokenEndpoint;
}
@Override
public String getAccessTokenEndpoint() {
return accessTokenEndpoint;
}
@Override
protected String getAuthorizationBaseUrl() {
return authorizationBaseUrl;
}
@Override
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
@Override
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
@Override
public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
}
}

View File

@@ -1,198 +1,81 @@
package org.openapitools.client.auth;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.oltu.oauth2.client.HttpClient;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import org.apache.oltu.oauth2.client.response.OAuthClientResponse;
import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory;
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 org.apache.oltu.oauth2.common.token.BasicOAuthToken;
import feign.Client;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.oauth.OAuth20Service;
import feign.Request.HttpMethod;
import feign.Request.Options;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.RetryableException;
import feign.Util;
import org.openapitools.client.StringUtil;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public abstract class OAuth implements RequestInterceptor {
public class OAuth implements RequestInterceptor {
static final int MILLIS_PER_SECOND = 1000;
static final int MILLIS_PER_SECOND = 1000;
public interface AccessTokenListener {
void notify(OAuth2AccessToken token);
}
public interface AccessTokenListener {
void notify(BasicOAuthToken token);
private volatile String accessToken;
private Long expirationTimeMillis;
private AccessTokenListener accessTokenListener;
protected OAuth20Service service;
protected String scopes;
protected String authorizationUrl;
protected String tokenUrl;
public OAuth(String authorizationUrl, String tokenUrl, String scopes) {
this.scopes = scopes;
this.authorizationUrl = authorizationUrl;
this.tokenUrl = tokenUrl;
}
@Override
public void apply(RequestTemplate template) {
// If the request already have an authorization (eg. Basic auth), do nothing
if (template.headers().containsKey("Authorization")) {
return;
}
private volatile String accessToken;
private Long expirationTimeMillis;
private OAuthClient oauthClient;
private TokenRequestBuilder tokenRequestBuilder;
private AuthenticationRequestBuilder authenticationRequestBuilder;
private AccessTokenListener accessTokenListener;
public OAuth(Client client, TokenRequestBuilder requestBuilder) {
this.oauthClient = new OAuthClient(new OAuthFeignClient(client));
this.tokenRequestBuilder = requestBuilder;
// If first time, get the token
if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
updateAccessToken(template);
}
public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));
switch(flow) {
case accessCode:
case implicit:
tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
break;
case password:
tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
break;
case application:
tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
break;
default:
break;
}
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
if (getAccessToken() != null) {
template.header("Authorization", "Bearer " + getAccessToken());
}
}
public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes);
private synchronized void updateAccessToken(RequestTemplate template) {
OAuth2AccessToken accessTokenResponse;
try {
accessTokenResponse = getOAuth2AccessToken();
} catch (Exception e) {
throw new RetryableException(0, e.getMessage(), HttpMethod.POST, e, null, template.request());
}
@Override
public void apply(RequestTemplate template) {
// If the request already have an authorization (eg. Basic auth), do nothing
if (template.headers().containsKey("Authorization")) {
return;
}
// If first time, get the token
if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
updateAccessToken(template);
}
if (getAccessToken() != null) {
template.header("Authorization", "Bearer " + getAccessToken());
}
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
if (accessTokenListener != null) {
accessTokenListener.notify(accessTokenResponse);
}
}
}
public synchronized void updateAccessToken(RequestTemplate template) {
OAuthJSONAccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
} catch (Exception e) {
throw new RetryableException(0, e.getMessage(), HttpMethod.POST, e, null, template.request());
}
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
if (accessTokenListener != null) {
accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
}
}
}
abstract OAuth2AccessToken getOAuth2AccessToken();
public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
this.accessTokenListener = accessTokenListener;
}
abstract OAuthFlow getFlow();
public synchronized String getAccessToken() {
return accessToken;
}
public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
this.accessTokenListener = accessTokenListener;
}
public synchronized void setAccessToken(String accessToken, Long expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = expiresIn == null ? null : System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
public synchronized String getAccessToken() {
return accessToken;
}
public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}
public synchronized void setAccessToken(String accessToken, Integer expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = expiresIn == null ? null : System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
return authenticationRequestBuilder;
}
public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
this.authenticationRequestBuilder = authenticationRequestBuilder;
}
public OAuthClient getOauthClient() {
return oauthClient;
}
public void setOauthClient(OAuthClient oauthClient) {
this.oauthClient = oauthClient;
}
public void setOauthClient(Client client) {
this.oauthClient = new OAuthClient( new OAuthFeignClient(client));
}
public static class OAuthFeignClient implements HttpClient {
private Client client;
public OAuthFeignClient() {
this.client = new Client.Default(null, null);
}
public OAuthFeignClient(Client client) {
this.client = client;
}
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
RequestTemplate req = new RequestTemplate()
.append(request.getLocationUri())
.method(requestMethod)
.body(request.getBody());
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
public void shutdown() {
// Nothing to do here
}
}
}
}

View File

@@ -13,6 +13,10 @@
package org.openapitools.client.auth;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public enum OAuthFlow {
accessCode, implicit, password, application
accessCode, //called authorizationCode in OpenAPI 3.0
implicit,
password,
application //called clientCredentials in OpenAPI 3.0
}

View File

@@ -0,0 +1,39 @@
package org.openapitools.client.auth;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public class OauthClientCredentialsGrant extends OAuth {
public OauthClientCredentialsGrant(String authorizationUrl, String tokenUrl, String scopes) {
super(authorizationUrl, tokenUrl, scopes);
}
@Override
protected OAuth2AccessToken getOAuth2AccessToken() {
try {
return service.getAccessTokenClientCredentialsGrant(scopes);
} catch (Exception e) {
throw new RuntimeException("Failed to get oauth token", e);
}
}
@Override
protected OAuthFlow getFlow() {
return OAuthFlow.application;
}
/**
* Configures the client credentials flow
*
* @param clientId
* @param clientSecret
*/
public void configure(String clientId, String clientSecret) {
service = new ServiceBuilder(clientId)
.apiSecret(clientSecret)
.defaultScope(scopes)
.build(new DefaultApi20Impl(authorizationUrl, tokenUrl));
}
}

View File

@@ -0,0 +1,48 @@
package org.openapitools.client.auth;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public class OauthPasswordGrant extends OAuth {
private String username;
private String password;
public OauthPasswordGrant(String tokenUrl, String scopes) {
super(null, tokenUrl, scopes);
}
@Override
protected OAuth2AccessToken getOAuth2AccessToken() {
try {
return service.getAccessTokenPasswordGrant(username, password);
} catch (Exception e) {
throw new RuntimeException("Failed to get oauth token", e);
}
}
@Override
protected OAuthFlow getFlow() {
return OAuthFlow.password;
}
/**
* Configures Oauth password grant flow
* Note: this flow is deprecated.
*
* @param username
* @param password
* @param clientId
* @param clientSecret
*/
public void configure(String username, String password, String clientId, String clientSecret) {
this.username = username;
this.password = password;
//TODO the clientId and secret are optional according with the RFC
service = new ServiceBuilder(clientId)
.apiSecret(clientSecret)
.defaultScope(scopes)
.build(new DefaultApi20Impl(authorizationUrl, tokenUrl));
}
}