okHttp 3.x authenticator is not getting called - java

I need to make a request through a proxy that needs authentication.
public class WebClient {
private final OkHttpClient httpClient;
private static WebClient webClient;
private WebClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (Configurator.getInstance().useProxy()) {
builder.proxySelector(new CustomProxySelector());
builder.authenticator((Route route, Response response) -> {
String credential = Credentials.basic("MYUSER", "MYPSW");
return response.request().newBuilder().header("Authorization", credential).build();
});
} else
builder.proxy(Proxy.NO_PROXY);
httpClient = builder
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
}
}
But using a debugger i see that the authenticator method never gets called and i receive 407 as response for any request.
However, when i use HttpURLConnection with Authenticator.setDefault, it works just fine and i can use my proxy authentication:
public boolean hasInternetConnection() throws IOException {
Request httpRequest = new Request.Builder().url("http://www.google.com/").build();
// This fails with 407
Response httpResponse = httpClient.newCall(httpRequest).execute();
java.net.Authenticator authenticator = new java.net.Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication("MYUSER", "MYPSW".toCharArray()));
}
};
java.net.Authenticator.setDefault(authenticator);
URL obj = new URL("http://www.google.com/");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
// This works with 200
int responseCode = con.getResponseCode();
return false;
}
So i think the question is: why OkHttpClient.Builder.authenticator method is not getting called?

As Gimby pointed, one of the issues was that i was calling the wrong method. What confused me was that sometimes the proxyAuthenticator was not getting called and i was trying to figure why.
The application i develop needs to access resources inside and outside my job network. Therefore, when i need external access, i must use a proxy with authentication. It works like this:
A request to an internet host is made;
The ProxySelector decides that the HTTP client should use the proxy for this
request, since it is an internet host;
Since the proxy is set, the ProxyAuthenticator gets called to send
the authorization header in the request.
However, when a request is made to an internal host, the ProxySelector decides that there is no need to use a proxy. Therefore, the ProxyAuthenticator does not get called, since there is no active proxy.
Here is my implementation to anyone interested:
WebClient.java
public class WebClient {
private final OkHttpClient httpClient;
private static WebClient webClient;
private WebClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (Configurator.getInstance().useProxy()) {
CodeUtils.setProxy();
builder.proxySelector(new CustomProxySelector());
builder.proxyAuthenticator(new CustomProxyAuthenticator());
} else {
builder.proxy(Proxy.NO_PROXY);
CodeUtils.removeProxy();
}
httpClient = builder
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
}
public static WebClient getInstance() {
return webClient != null ? webClient : (webClient = new WebClient());
}
public static void reload() {
webClient = null;
}
public String doGet(String url) throws IOException {
Request httpRequest = new Request.Builder().url(url).build();
Response httpResponse = httpClient.newCall(httpRequest).execute();
if (httpResponse.code() != 200) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("success", false);
jsonObject.put("msg", httpResponse.body().string());
jsonObject.put("httpCode", httpResponse.code());
return jsonObject.toString();
}
return httpResponse.body().string();
}
public String doPost(String url, JSONObject body) throws IOException {
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body.toString());
Request request = new Request.Builder()
.header("Accept", "application/json")
.header("Content-type", "application/json; charset=UTF-8")
.url(url)
.post(requestBody).build();
Response response = httpClient.newCall(request).execute();
return response.body().string();
}
}
CustomProxyAuthenticator.java
public class CustomProxyAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
String username = Configurator.getInstance().getProxyUser();
String password = Configurator.getInstance().getProxyPassword();
String credential = Credentials.basic(username, password);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
}
CustomProxySelector.java
public class CustomProxySelector extends ProxySelector {
private Configurator configurator = Configurator.getInstance();
private List<String> nonProxyHosts = Arrays.asList(configurator.getNonProxyHosts().split("\\|"));
private String proxyHost = configurator.getProxyHost();
private int proxyPort = configurator.getProxyPort();
#Override
public List<Proxy> select(URI uri) {
final List<Proxy> proxyList = new ArrayList<>(1);
String host = uri.getHost();
if (host.startsWith("127.0.0.1") || nonProxyHosts.contains(host))
proxyList.add(Proxy.NO_PROXY);
else
proxyList.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
return proxyList;
}
#Override
public void connectFailed(URI arg0, SocketAddress arg1, IOException arg2) {
}
}

Related

Unable to Refresh Bearer Token using Retrofit

I'm using Retrofit and OkHttp in my Project and i want to refresh token when the server gives 401 error code and store the new token in shared preference and make the same call again without notifing the user. Token type is bearer and expires every hour.
But the output that i desired is not coming, when the token expires the authenticator does nothing.
ApiInterface.java
public interface ApiInterface {
#FormUrlEncoded
#POST("auth/login")
Call<ResponseBody> login(#FieldMap HashMap<String, Object> map);
#FormUrlEncoded
#POST("auth/register")
Call<ResponseBody> register(#FieldMap HashMap<String, Object> map);
#FormUrlEncoded
#POST("auth/forgotpassword")
Call<ResponseBody> forgotPassword(#FieldMap HashMap<String, Object> map);
#POST("auth/refresh")
Call<ResponseBody> refreshToken(#Header("Authorization") String token);
#POST("auth/GetAllInterests")
Call<ResponseBody> getAllInterest();
#POST("auth/AddUserProfile")
#Multipart
Call<ResponseBody> addUserProfile(
#Part("display_name") RequestBody display_name,
#Part("children_age_show") RequestBody children_age_show,
#Part("address") RequestBody address,
#Part("no_of_children") RequestBody no_of_children,
#Part("children_age") RequestBody children_age,
#Part("date_of_birth") RequestBody date_of_birth,
#Part("interests") RequestBody interests,
#Part List<MultipartBody.Part> images
);
#POST("auth/GetUserProfile")
Call<ResponseBody> getUserProfile();
#FormUrlEncoded
#POST("auth/ChangePassword")
Call<ResponseBody> changePassword(#FieldMap HashMap<String, Object> map);
#POST("auth/logout")
Call<ResponseBody> logout();
}
ApiClient.java
public class ApiClient {
private static Retrofit retrofit = null;
private static ApiInterface apiInterface = null;
public static Retrofit getRetrofit(Context context) {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (AppPreference.getPreference(context, AppPersistence.keys.AUTH_TOKEN) != null) {
request = request.newBuilder()
.addHeader("Authorization", AppPreference.getPreference(context, AppPersistence.keys.AUTH_TOKEN))
.build();
}
return chain.proceed(request);
}
})
.authenticator(new TokenAuthenticator(context))
.build();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
}
return retrofit;
}
public static ApiInterface getApiInterface(Context context) {
if (apiInterface == null) {
getRetrofit(context);
apiInterface = retrofit.create(ApiInterface.class);
}
return apiInterface;
}
}
TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {
Context context;
public TokenAuthenticator(Context context) {
this.context = context;
}
#Override
public Request authenticate(Route route, Response response) throws IOException {
Log.d("Authenticator", "Authenticator Called");
if (response.code() == 401) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
Log.d("Authenticator", "Requesting for New Token");
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
retrofit2.Response<ResponseBody> response1 = apiInterface
.refreshToken(AppPreference.getPreference(context, AppPersistence.keys.AUTH_TOKEN))
.execute();
if (response1.isSuccessful()) {
try {
String body = response1.body().string();
JSONObject jsonObject = new JSONObject(body);
if (jsonObject.getBoolean("status")) {
String newToken = "Bearer " + jsonObject.getJSONObject("Data").getString("access_token");
AppPreference.setPreference(context, AppPersistence.keys.AUTH_TOKEN, newToken);
Log.d("Authenticator", "New Token Generated");
return response.request().newBuilder()
.header("Authorization", newToken)
.build();
}
} catch (Exception e) {
Log.e("TokenAuthenticator", e.getMessage(), e);
}
}
Toast.makeText(context, response1.body().string(), Toast.LENGTH_SHORT).show();
}
return null;
}
}
Please try the below way
(1) HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
try {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder().build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
});
} catch (Exception e) {
e.printStackTrace();
}
String baseUrl = "";
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
(2) public class TokenAuthenticator implements Authenticator {
private final ApiService apiservice;
public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
this.tokenServiceHolder = tokenServiceHolder;
}
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
newtoken = apiservice.refreshToken().execute();
return response.request().newBuilder()
.header(AUTHORIZATIONKEY, newtoken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
}
Hope it may help you

Curl to okhttp conversion

I have a curl of the form curl -v -u username:password -H "Content-Type: application/json"........
Not able to crack how to get the -u part working. Have tried multiple options like
Authenticator proxyAuthenticator = new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.addHeader("Proxy-Authorization", credential)
.addHeader("Content-Type", "application/json")
.build();
}
};
and
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
RequestBody requestBody = new FormBody.Builder().add("username", "password").build();
Request request = original.newBuilder()
.addHeader("Content-Type", "application/json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
Both of which are not working. Not able to get it working even by posting it as header. Please help out!
Solved it by this.
protected Authenticator getBasicAuth(final String username, final String password) {
return new Authenticator() {
private int mCounter = 0;
#Override
public Request authenticate(Route route, Response response) throws IOException {
if (mCounter++ > 0) {
return null;
} else {
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Authorization", credential).build();
}
}
};
}
then added this as authenticator to my client.

Automatic custom authentication

I want to use Apache HttpClient 4+ to send authenticated requests to an HTTP server (actually, I need this for different server implementations) AND to authenticate (or re-authenticate) automatically ONLY when it is required, when auth token is not present or it is dead.
In order to authenticate I need to send a POST request with JSON containing user credentials.
In case authentication token is not provided in the cookie, one server returns status code 401, another one 500 with AUTH_REQUIRED text in the response body.
I played a lot with different HttpClient versions by setting CredentialsProvider with proper Credentials, trying to implement own AuthScheme and registering it and unregistering the rest of standard ones.
I also tried to set own AuthenticationHandler. When isAuthenticationRequested is called I'm analyzing HttpResponse which is passed as the method argument and decided what to return by analyzing status code and response body. I expected that this (isAuthenticationRequested() == true) is what force the client to authenticate by calling AuthScheme.authenticate (my AuthScheme implementation which is returned by AuthenticationHandler.selectScheme), but instead of AuthScheme.authenticate invocation I can see AuthenticationHandler.getChallenges. I really don't know what I should return by this method, thus I'm just returning new HashMap<>().
Here is debug output I have in result
DEBUG org.apache.http.impl.client.DefaultHttpClient - Authentication required
DEBUG org.apache.http.impl.client.DefaultHttpClient - example.com requested authentication
DEBUG com.test.httpclient.MyAuthenticationHandler - MyAuthenticationHandler.getChallenges()
DEBUG org.apache.http.impl.client.DefaultHttpClient - Response contains no authentication challenges
What should I do next? Am I moving in the right direction?
UPDATE
I've almost achieved what I needed. Unfortunately, I can't provide fully working project sources, because I can't provide public access to my server. Here is my simplified code example:
MyAuthScheme.java
public class MyAuthScheme implements ContextAwareAuthScheme {
public static final String NAME = "myscheme";
#Override
public Header authenticate(Credentials credentials,
HttpRequest request,
HttpContext context) throws AuthenticationException {
HttpClientContext clientContext = ((HttpClientContext) context);
String name = clientContext.getTargetAuthState().getState().name();
// Hack #1:
// I've come to this check. I don't like it, but it allows to authenticate
// first request and don't repeat authentication procedure for further
// requests
if(name.equals("CHALLENGED") && clientContext.getResponse() == null) {
//
// auth procedure must be here but is omitted in current example
//
// Hack #2: first request won't be present with auth token cookie set via cookie store
request.setHeader(new BasicHeader("Cookie", "MYAUTHTOKEN=bru99rshi7r5ucstkj1wei4fshsd"));
// this works for second and subsequent requests
BasicClientCookie authTokenCookie = new BasicClientCookie("MYAUTHTOKEN", "bru99rshi7r5ucstkj1wei4fshsd");
authTokenCookie.setDomain("example.com");
authTokenCookie.setPath("/");
BasicCookieStore cookieStore = (BasicCookieStore) clientContext.getCookieStore();
cookieStore.addCookie(authTokenCookie);
}
// I can't return cookie header here, otherwise it will clear
// other cookies, right?
return null;
}
#Override
public void processChallenge(Header header) throws MalformedChallengeException {
}
#Override
public String getSchemeName() {
return NAME;
}
#Override
public String getParameter(String name) {
return null;
}
#Override
public String getRealm() {
return null;
}
#Override
public boolean isConnectionBased() {
return false;
}
#Override
public boolean isComplete() {
return true;
}
#Override
public Header authenticate(Credentials credentials,
HttpRequest request) throws AuthenticationException {
return null;
}
}
MyAuthStrategy.java
public class MyAuthStrategy implements AuthenticationStrategy {
#Override
public boolean isAuthenticationRequested(HttpHost authhost,
HttpResponse response,
HttpContext context) {
return response.getStatusLine().getStatusCode() == 401;
}
#Override
public Map<String, Header> getChallenges(HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
Map<String, Header> challenges = new HashMap<>();
challenges.put(MyAuthScheme.NAME, new BasicHeader(
"WWW-Authentication",
"Myscheme realm=\"My SOAP authentication\""));
return challenges;
}
#Override
public Queue<AuthOption> select(Map<String, Header> challenges,
HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
Credentials credentials = ((HttpClientContext) context)
.getCredentialsProvider()
.getCredentials(new AuthScope(authhost));
Queue<AuthOption> authOptions = new LinkedList<>();
authOptions.add(new AuthOption(new MyAuthScheme(), credentials));
return authOptions;
}
#Override
public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
#Override
public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
}
MyApp.java
public class MyApp {
public static void main(String[] args) throws IOException {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
Credentials credentials = new UsernamePasswordCredentials("user#example.com", "secret");
credsProvider.setCredentials(AuthScope.ANY, credentials);
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(new BasicCookieStore());
context.setCredentialsProvider(credsProvider);
CloseableHttpClient client = HttpClientBuilder.create()
// my server requires this header otherwise it returns response with code 500
.setDefaultHeaders(Collections.singleton(new BasicHeader("x-requested-with", "XMLHttpRequest")))
.setTargetAuthenticationStrategy(new MyAuthStrategy())
.build();
String url = "https://example.com/some/resource";
String url2 = "https://example.com/another/resource";
// ======= REQUEST 1 =======
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request, context);
String responseText = EntityUtils.toString(response.getEntity());
request.reset();
// ======= REQUEST 2 =======
HttpGet request2 = new HttpGet(url);
HttpResponse response2 = client.execute(request2, context);
String responseText2 = EntityUtils.toString(response2.getEntity());
request2.reset();
// ======= REQUEST 3 =======
HttpGet request3 = new HttpGet(url2);
HttpResponse response3 = client.execute(request3, context);
String responseText3 = EntityUtils.toString(response3.getEntity());
request3.reset();
client.close();
}
}
Versions
httpcore: 4.4.6
httpclient: 4.5.3
Probably this is not the best code but at least it works.
Please look at my comments in MyAuthScheme.authenticate() method.
This works as expected for me with Apache HttpClient 4.2
NOTE. Though it is compiled and executed with httpclient 4.5, its execution falls into forever loop.
MyAuthScheme.java
public class MyAuthScheme implements ContextAwareAuthScheme {
public static final String NAME = "myscheme";
private static final String REQUEST_BODY = "{\"login\":\"%s\",\"password\":\"%s\"}";
private final URI loginUri;
public MyAuthScheme(URI uri) {
loginUri = uri;
}
#Override
public Header authenticate(Credentials credentials,
HttpRequest request,
HttpContext context) throws AuthenticationException {
BasicCookieStore cookieStore = (BasicCookieStore) context.getAttribute(ClientContext.COOKIE_STORE);
DefaultHttpClient client = new DefaultHttpClient();
// authentication cookie is set automatically when
// login response arrived
client.setCookieStore(cookieStore);
HttpPost loginRequest = new HttpPost(loginUri);
String requestBody = String.format(
REQUEST_BODY,
credentials.getUserPrincipal().getName(),
credentials.getPassword());
loginRequest.setHeader("Content-Type", "application/json");
try {
loginRequest.setEntity(new StringEntity(requestBody));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
HttpResponse response = client.execute(loginRequest);
int code = response.getStatusLine().getStatusCode();
EntityUtils.consume(response.getEntity());
if(code != 200) {
throw new IllegalStateException("Authentication problem");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
loginRequest.reset();
}
return null;
}
#Override
public void processChallenge(Header header) throws MalformedChallengeException {}
#Override
public String getSchemeName() {
return NAME;
}
#Override
public String getParameter(String name) {
return null;
}
#Override
public String getRealm() {
return null;
}
#Override
public boolean isConnectionBased() {
return false;
}
#Override
public boolean isComplete() {
return false;
}
#Override
public Header authenticate(Credentials credentials,
HttpRequest request) throws AuthenticationException {
// not implemented
return null;
}
}
MyAuthSchemeFactory.java
public class MyAuthSchemeFactory implements AuthSchemeFactory {
private final URI loginUri;
public MyAuthSchemeFactory(URI uri) {
this.loginUri = uri;
}
#Override
public AuthScheme newInstance(HttpParams params) {
return new MyAuthScheme(loginUri);
}
}
MyAuthStrategy.java
public class MyAuthStrategy implements AuthenticationStrategy {
#Override
public boolean isAuthenticationRequested(HttpHost authhost,
HttpResponse response,
HttpContext context) {
return response.getStatusLine().getStatusCode() == 401;
}
#Override
public Map<String, Header> getChallenges(HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
Map<String, Header> challenges = new HashMap<>();
challenges.put("myscheme", new BasicHeader("WWW-Authenticate", "myscheme"));
return challenges;
}
#Override
public Queue<AuthOption> select(Map<String, Header> challenges,
HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute(ClientContext.AUTHSCHEME_REGISTRY);
AuthScheme authScheme = registry.getAuthScheme(MyAuthScheme.NAME, new BasicHttpParams());
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
Credentials credentials = credsProvider.getCredentials(new AuthScope(authhost));
Queue<AuthOption> options = new LinkedList<>();
options.add(new AuthOption(authScheme, credentials));
return options;
}
#Override
public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
#Override
public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
}
App.java
public class App {
public static void main(String[] args) throws IOException, URISyntaxException {
URI loginUri = new URI("https://example.com/api/v3/users/login");
AuthSchemeRegistry schemeRegistry = new AuthSchemeRegistry();
schemeRegistry.register(MyAuthScheme.NAME, new MyAuthSchemeFactory(loginUri));
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope("example.com", 8065),
new UsernamePasswordCredentials("user1#example.com", "secret"));
DefaultHttpClient client = new DefaultHttpClient();
client.setCredentialsProvider(credentialsProvider);
client.setTargetAuthenticationStrategy(new MyAuthStrategy());
client.setAuthSchemes(schemeRegistry);
client.setCookieStore(new BasicCookieStore());
String getResourcesUrl = "https://example.com:8065/api/v3/myresources/";
HttpGet getResourcesRequest = new HttpGet(getResourcesUrl);
getResourcesRequest.setHeader("x-requested-with", "XMLHttpRequest");
try {
HttpResponse response = client.execute(getResourcesRequest);
// consume response
} finally {
getResourcesRequest.reset();
}
// further requests won't call MyAuthScheme.authenticate()
HttpGet getResourcesRequest2 = new HttpGet(getResourcesUrl);
getResourcesRequest2.setHeader("x-requested-with", "XMLHttpRequest");
try {
HttpResponse response2 = client.execute(getResourcesRequest);
// consume response
} finally {
getResourcesRequest2.reset();
}
HttpGet getResourcesRequest3 = new HttpGet(getResourcesUrl);
getResourcesRequest3.setHeader("x-requested-with", "XMLHttpRequest");
try {
HttpResponse response3 = client.execute(getResourcesRequest);
// consume response
} finally {
getResourcesRequest3.reset();
}
}
}

Design pattern for retrofit interface

I have trouble with the design of my Retrofit interface creator. I want to be able to instanciate the API interface in a generic way and update the corresponding instance whenever a token is passed. Currently, when I update the token, I have to call createService() method again to get the new instance that used the token in the generation of the Interface...
Somebody asked for a similar question but never got an answer here
public class RetrofitCreator {
private static String TAG = "RetrofitCreator";
private static String WSSE = null;
private static String AmzToken = null;
static HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
private static AmazonAPI amazonAPI = null;
private static VanishAPI cobaltAPI = null;
//static OkHttpClient client = new OkHttpClient.Builder().build();
static OkHttpClient.Builder httpClient = new OkHttpClient.Builder().addInterceptor(interceptor.setLevel(HttpLoggingInterceptor.Level.BODY));
private static Retrofit.Builder builder =
new Retrofit.Builder();
public static <S> S createService(Class<S> serviceClass) {
S mAPI = null;
if(serviceClass.getSimpleName().equals("VanishAPI")){
if(VanishAPI==null){
VanishAPI = (VanishAPI) createVanishAPI(serviceClass);
}
mAPI = (S) VanishAPI;
}else if(serviceClass.getSimpleName().equals("AmazonAPI")){
if(amazonAPI==null){
amazonAPI = (AmazonAPI) createAmazonAPI(serviceClass);
}
mAPI = (S) amazonAPI;
}
return mAPI;
}
public static void setWSSE(String WSSE) {
RetrofitCreator.WSSE = WSSE;
vanishAPI = createVanishAPI(VanishAPI.class);
}
public static void setAmzToken(String token) {
RetrofitCreator.AmzToken = token;
amazonAPI = createAmazonAPI(AmazonAPI.class);
}
private static <S> S createAmazonAPI(Class<S> serviceClass){
httpClient = getUnsafeOkHttpClient();
builder = new Retrofit.Builder()
.baseUrl(Constants.URL_AMAZON)
.addConverterFactory(JacksonConverterFactory.create());
if (AmzToken != null) {
Log.w(TAG, "WSSE not null!");
Interceptor interceptorSecure = new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Cache-Control", "no-cache")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + AmzToken)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
};
httpClient.addInterceptor(interceptorSecure);
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
(...)
}
To get it in each Activity I use :
amazonApi = RetrofitCreator.createService(AmazonAPI.class);
Make your interceptor look like this:
public class TokenInterceptor implements Interceptor {
private String token;
public String getToken() {
return token;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Cache-Control", "no-cache")
.header("Accept", "application/json")
.method(original.method(), original.body());
if (getToken() != null) {
requestBuilder.header("Authorization", "Bearer " + AmzToken);
}
Request request = requestBuilder.build();
return chain.proceed(request);
}
public void setToken(String token) {
this.token = token;
}
}
Add it to you OkHttp client and keep reference to this interceptor.
Now you don't have to call createService() after each token change. Just change token in interceptor via interceptor.setToken()

DefaultHttpClient - Avoiding multiple Attempts to execute request [duplicate]

Is there an easier way to setup the http client for preemptive basic authentication than what described here?
In previous version (3.x) it used to be a simple method call (eg, httpClient.getParams().setAuthenticationPreemptive(true)).
The main thing I want to avoid is adding the BasicHttpContext to each method I execute.
If you are looking to force HttpClient 4 to authenticate with a single request, the following will work:
String username = ...
String password = ...
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password);
HttpRequest request = ...
request.addHeader(new BasicScheme().authenticate(creds, request));
It's difficult to do this without passing a context through every time, but you can probably do it by using a request interceptor. Here is some code that we use (found from their JIRA, iirc):
// Pre-emptive authentication to speed things up
BasicHttpContext localContext = new BasicHttpContext();
BasicScheme basicAuth = new BasicScheme();
localContext.setAttribute("preemptive-auth", basicAuth);
httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);
(...)
static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
// If no auth scheme avaialble yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
if (authScheme != null) {
Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
if (creds == null) {
throw new HttpException("No credentials for preemptive authentication");
}
authState.setAuthScheme(authScheme);
authState.setCredentials(creds);
}
}
}
}
This is the same solution as Mat's Mannion's, but you don't have to put localContext to each request. It's simpler, but it adds authentication to ALL requests. Useful, if you don't have control over individual requests, as in my case when using Apache Solr, which uses HttpClient internally.
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);
(...)
static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme available yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
if (creds == null) {
throw new HttpException("No credentials for preemptive authentication");
}
authState.update(new BasicScheme(), creds);
}
}
}
Of course, you have to set the credentials provider:
httpClient.getCredentialsProvider().setCredentials(
new AuthScope(url.getHost(), url.getPort()),
new UsernamePasswordCredentials(username, password))
The AuthScope must not contain realm, as it is not known in advance.
A lot of the answers above use deprecated code. I am using Apache SOLRJ version 5.0.0.
My code consists of
private HttpSolrClient solrClient;
private void initialiseSOLRClient() {
URL solrURL = null;
try {
solrURL = new URL(urlString);
} catch (MalformedURLException e) {
LOG.error("Cannot parse the SOLR URL!!" + urlString);
throw new SystemException("Cannot parse the SOLR URL!! " + urlString, e);
}
String host = solrURL.getHost();
int port = solrURL.getPort();
AuthScope authScope = new AuthScope(host, port);
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("red bananas in the spring");
String decryptPass = textEncryptor.decrypt(pass);
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(userName, decryptPass);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
authScope,
creds);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.addInterceptorFirst(new PreemptiveAuthInterceptor());
builder.setDefaultCredentialsProvider(credsProvider);
CloseableHttpClient httpClient = builder.build();
solrClient = new HttpSolrClient(urlString, httpClient);
}
The PreemptiveAuthInterceptor is now as follows:-
static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme available yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
CredentialsProvider credsProvider = (CredentialsProvider)
context.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
if(creds == null){
}
authState.update(new BasicScheme(), creds);
}
}
}
A little late to the party but I came accross the thread trying to solve this for proxy pre-authorization of a post request. To add to Adam's response, I found the following worked for me:
HttpPost httppost = new HttpPost(url);
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password);
Header bs = new BasicScheme().authenticate(creds, httppost);
httppost.addHeader("Proxy-Authorization", bs.getValue());
Thought that might be helpful for anyone else who runs into this.
I think the best way may be to just do it manually. I added the following function
Classic Java:
import javax.xml.bind.DatatypeConverter;
...
private static void addAuthHeader(HttpRequestBase http, String username, String password) throws UnsupportedEncodingException {
String encoded = DatatypeConverter.printBase64Binary((username + ":" + password).getBytes("UTF-8"));
http.addHeader("AUTHORIZATION", "Basic " + encoded);
}
HTTPRequestBase can be an instance of HttpGet or HttpPost
Android:
import android.util.Base64;
...
private static void addAuthHeader(HttpRequestBase http, String username, String password) throws UnsupportedEncodingException {
String encoded = Base64.encodeToString((username + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP);
http.addHeader("AUTHORIZATION", "Basic " + encoded);
}
I'm using this code, based on my reading of the HTTPClient 4.5 docs:
HttpClientContext ctx = HttpClientContext.create()
ctx.setCredentialsProvider(new BasicCredentialsProvider())
ctx.setAuthCache(new BasicAuthCache())
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user, pass)
AuthScope authScope = new AuthScope(host, port)
ctx.getCredentialsProvider.setCredentials(authScope, credentials)
// This part makes authentication preemptive:
HttpHost targetHost = new HttpHost(host, port, scheme)
ctx.getAuthCache.put(targetHost, new BasicScheme())
...and make sure you always pass that context to HTTPClient.execute().
I don't quite get your closing comment. It's the HttpClient that has all of that machinery for doing preemptive auth, and you only have to do that once (when you construct and configure your HttpClient). Once you've done that, you construct your method instances the same way as always. You don't "add the BasicHttpContext" to the method.
Your best bet, I'd think, is to have your own object that sets up all of the junk required for preemptive auth, and has a simple method or methods for executing requests on given HTTPMethod objects.
in android,Mat Mannion's answer can't resolve https,still send two requests,you can do like below,the trick is append authHeader with user-agent:
public static DefaultHttpClient createProxyHttpClient() {
try {
final DefaultHttpClient client = createPlaintHttpClient();
client.setRoutePlanner(new HttpRoutePlanner() {
#Override
public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
boolean isSecure = "https".equalsIgnoreCase(target.getSchemeName());
if (needProxy) {
Header header = isSecure ? ProxyUtils.createHttpsAuthHeader() : ProxyUtils.createAuthHeader();
if (isSecure) {
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT + "\r\n" + header.getName() + ":" + header.getValue());
} else {
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
if (request instanceof RequestWrapper) {
request = ((RequestWrapper) request).getOriginal();
}
request.setHeader(header);
}
String host = isSecure ? ProxyUtils.SECURE_HOST : ProxyUtils.HOST;
int port = isSecure ? ProxyUtils.SECURE_PORT : ProxyUtils.PORT;
return new HttpRoute(target, null, new HttpHost(host, port), isSecure);
} else {
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
return new HttpRoute(target, null, isSecure);
}
}
});
return client;
} catch (Exception e) {
e.printStackTrace();
return new DefaultHttpClient();
}
}
public static DefaultHttpClient createPlaintHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
PlainSSLSocketFactory socketFactory = new PlainSSLSocketFactory(trustStore);
socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
BasicHttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 30000);
HttpConnectionParams.setSoTimeout(params, 30000);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", socketFactory, 443));
ThreadSafeClientConnManager ccm = new ThreadSafeClientConnManager(params, registry);
HttpClientParams.setCookiePolicy(params, CookiePolicy.BROWSER_COMPATIBILITY);
final DefaultHttpClient client = new DefaultHttpClient(ccm, params);
client.setRoutePlanner(new HttpRoutePlanner() {
#Override
public HttpRoute determineRoute(HttpHost target, HttpRequest arg1, HttpContext arg2) throws HttpException {
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
return new HttpRoute(target, null, "https".equalsIgnoreCase(target.getSchemeName()));
}
});
return client;
} catch (Exception e) {
e.printStackTrace();
return new DefaultHttpClient();
}
}
SolrConfig:
#Configuration
public class SolrConfig {
#Value("${solr.http.url}")
private String solrUrl;
#Value("${solr.http.username}")
private String solrUser;
#Value("${solr.http.password}")
private String solrPassword;
#Value("${solr.http.pool.maxTotal}")
private int poolMaxTotal;
#Value("${solr.http.pool.maxPerRoute}")
private int pollMaxPerRoute;
#Bean
public SolrClient solrClient() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(poolMaxTotal);
connectionManager.setDefaultMaxPerRoute(pollMaxPerRoute);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(solrUser, solrPassword));
CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new PreemptiveAuthInterceptor())
.setConnectionManager(connectionManager)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
return new HttpSolrClient.Builder(solrUrl).withHttpClient(httpClient).build();
}
}
PreemptiveAuthInterceptor:
public class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context)
throws HttpException {
AuthState authState = (AuthState) context
.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme available yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
CredentialsProvider credentialsProvider = (CredentialsProvider) context
.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context
.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
Credentials credentials = credentialsProvider.getCredentials(new AuthScope(
targetHost.getHostName(), targetHost.getPort()));
if (credentials == null) {
throw new HttpException(
"No credentials for preemptive authentication");
}
authState.update(new BasicScheme(), credentials);
}
}
}

Categories

Resources