I've looked around at several different questions/answers here on SO regarding the XmlPullParserException in Android using KSOAP2 but so far nothing has helped me figure out what's wrong. I'm starting to port over my iOS App to Android, and right now I'm stuck on this part of getting the app to communicate with a .Net Web Service. Below is the code in question, most of which was pieced together from various different questions/blogs/sites since I also have a self signed certificate on the Web Service. If anyone has any tips or can point me in the direction of some reading that will help me figure this out that would be great since there are several other areas of the app the use the Web Service and I know I'm going to need to know how to debug this better than what I'm getting now.
String URL = "https://online.ensinet.com/Services/ENSIMobileservice.asmx";
String SOAP_ACTION = "http://ensinet.com/VerifyRep";
String NAMESPACE = "http://ensinet.com/";
String METHOD_NAME = "VerifyRep";
String SERVER = "online.ensinet.com";
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
//PropertyInfo usernamePI = new PropertyInfo();
//usernamePI.setName("RepLogin");
//usernamePI.setValue(username);
//usernamePI.setType(String.class);
request.addProperty("RepLogin", username);
//PropertyInfo passPI = new PropertyInfo();
//passPI.setName("RepPass");
//passPI.setValue(passphrase);
//passPI.setType(String.class);
request.addProperty("RepPass",passphrase);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
try
{
allowAllSSL();
HttpsTransportSE androidHttpTransport = new HttpsTransportSE(SERVER,443, URL, 1000);
androidHttpTransport.call(SOAP_ACTION,envelope);
SoapObject response=(SoapObject) envelope.getResponse();
Log.i("Message", "the response contains: " + response.toString());
}
catch(Exception e)
{
Log.i("Message", "there was an error: " + e);
}
Okay so here's a little bit more information. First the allowAllSSL() is a method I found in another forum to bypass the credential manager of a self signed certificate since I don't have the credentials and the information that is being gathered by the mobile device isn't sensitive like the Web Application where all the Web Services are hosted. Below is the method in detail that sets up a fake credential manager
private static TrustManager[] trustManagers;
public static class _FakeX509TrustManager implements
javax.net.ssl.X509TrustManager {
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return (true);
}
public boolean isServerTrusted(X509Certificate[] chain) {
return (true);
}
public X509Certificate[] getAcceptedIssuers() {
return (_AcceptedIssuers);
}
}
public static void allowAllSSL() {
javax.net.ssl.HttpsURLConnection
.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
javax.net.ssl.SSLContext context = null;
if (trustManagers == null) {
trustManagers = new javax.net.ssl.TrustManager[] { new _FakeX509TrustManager() };
}
try {
context = javax.net.ssl.SSLContext.getInstance("SSL");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
Log.e("allowAllSSL", e.toString());
} catch (KeyManagementException e) {
Log.e("allowAllSSL", e.toString());
}
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
Also, I took a look at the requestDump and I noticed that in the request there's a <Header /> tag that isn't part of the actual Web Service request so I think this is being added by KSOAP2, and so far I haven't seen a way to remove it which may be part of the START TAG erro that I'm getting. I think I'm going to have the build the SOAP request manually, which I will try later tonight to see if that works.
Okay so I was able to figure out the problem. It looks like the android side is a bit more specific than the iOS side since the problem was the URL that I was using. In the code above I was using the URL of the complete list of Operations that are available for the Mobile Application like in the iOS code, but I needed to use the actual URL of the Operation I was calling (so I needed to add ?op=VerifyRep to the end of the URL string above). Now I'm getting the right responses, so it's off to the next step and trying to figure out how the parse out the data needed.
Related
in my application I am trying to do a HTTPS POST request to my server.
However, I keep getting SSLHandshakeException - Chain chain validation failed, all the time. I tried to send a request using POSTMAN and I got a response from the server. What can be causing this error when I try to send the request from the application?
Here a code snippet where I try to send the post request:
public static JSONObject getDataLibConfiguration(Context context) throws HttpRequestException {
int statusCode = 0;
JSONObject commonInformation;
HttpsURLConnection connection = null;
try {
commonInformation = ConfigurationProcessor.getCommonInformation(context);
if (commonInformation == null) {
return null;
}
URL url = new URL(BuildConfig.SERVER_CONFIG_URL);
if (BuildConfig.DEBUG) {
LogUtils.d(TAG, "url = " + url.getPath());
}
connection = getHttpsConnection(url);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Content-Encoding", "gzip");
byte[] gzipped = HttpUtils.gzip(commonInformation.toString());
cos = new CountingOutputStream(connection.getOutputStream()); //<-- This is where I get the exception
cos.write(gzipped);
cos.flush();
statusCode = connection.getResponseCode();
// More code her
}
private static HttpsURLConnection getHttpsConnection(URL url) throws IOException, GeneralSecurityException {
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
MatchDomainTrustManager myTrustManager = new MatchDomainTrustManager(url.getHost());
TrustManager[] tms = new TrustManager[]{myTrustManager};
sslContext.init(null, tms, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
connection.setSSLSocketFactory(sslSocketFactory);
} catch (AssertionError ex) {
if (BuildConfig.DEBUG) {
LogFileUtils.e(TAG, "Exception in getHttpsConnection: " + ex.getMessage());
}
LogUtils.e(TAG, "Exception: " + ex.toString());
}
return connection;
}
In my case it was wrong date on phone.
Fixing date resolved an issue
If you're using an emulated device it may solve the problem if you just 'Cold Boot' it.
Sometimes the date on those things can get stuck if you let them run for some time, which results in this expired-certificate-problem.
The problem was that the certificate was expired.
In my case, I fetch this issue on Android Emulator.
When I clear emulator cache has resolved the issue.
My date and time were correct, but I didn't have "Use Network Provided Time checked" in my system settings.
I fixed this issue by going to Settings > Date and Time > Check "Use network-provided time" and also check "Use network-provided time zone".
Then this error went away.
In my case, the issue was with the phone date. So please check it, set to automatic.
public static void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
or check system date of your device - I had this Exception when I tried to connect with wrong date!..
If anyone come across this issue pertaining to specific device, then the reason should be because of incorrect date set in the device.
I fixed this error by resetting my emulator date and time. My server is working fine just I changed the date and time of my emulator as current server time zone.
#Yash Bhardwaj in the comment on #Vadim answer said that the problem was in Glide framework. I faced the same problem: Https requests to server using Ktor framework were all successful, but when Glide tried to load image from the same server, it faced the SSLHandshakeException.
To solve this issue you should look here: Solve Glide SSLHandshakeException.
To make a deal with #GlideModule annotation you should import kapt plugin and add these dependencies into your app build.gradle:
implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
If you use android emulator, you can wipe data and run again, it works
I am currently working on a project where I am sending data via an https call to our server api. The base URL for the project supports ssl (Our url api endpoint starts with https://api.....). I am using Retrofit 2 and OkHttp3 and am setting up the client like this:
public static void buildClient(){
//Misc code here.... not showing for security reasons.
OkHttpClient client = RetrofitClient.configureClient(new OkHttpClient());
//I make calls here to update interceptors, timeouts, etc.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.client(client)
.build();
}
//Setup the ssl stuff here
public static OkHttpClient configureClient(final OkHttpClient client) {
final TrustManager[] certs = new TrustManager[]{new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType)
throws CertificateException {
}
#Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType)
throws CertificateException {
}
}};
SSLContext ssl = null;
try {
ssl = SSLContext.getInstance("TLS");
ssl.init(null, certs, new SecureRandom());
} catch (final java.security.GeneralSecurityException ex) {
}
try {
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(final String hostname,
final SSLSession session) {
return true;
}
};
client.setHostnameVerifier(hostnameVerifier);
client.setSslSocketFactory(ssl.getSocketFactory());
} catch (final Exception e) {
}
return client;
}
So after this, we are all set.
Now, here's what I know:
1) I am sending via HTTPS because if I were not, the server would throw an error, which it is not.
2) My code is working just fine in that it is communicating with the server and the app will work.
The problem here is that the actual Body data is not being encrypted. Here are 2 photo examples to show what I mean.
1)
2)
The first image shows proper obfuscation of the actual body data in that the data is being converted to encrypted 'stuff' while the second shows plain text. The second one is me sending a POST call to the server with an object.
My question is, how do I go about replicating this so that my body text is hidden / encrypted like the other?
Notes:
1) I am using obfuscation via Proguard
2) I to have minifyEnabled set to true
3) How I found this out is via a packet sniffer
Anyone have any ideas how to accomplish this? Or can anyone point me in the right direction as to what this is called specifically?
Thanks.
EDIT:
So, it looks like I was not understanding a key component here.
Short answer is, the call is already encrypted and is sending Https.
Long answer is, I have been comparing my data calls to ones like these:
1)
2)
Where I just assumed that These were encrypted, while mine was not. It turns out that the calls I am sending are encrypted just fine, as are these, but this data is zipped / compressed, which makes it unreadable to the eye, which is what made me think that it was what encrypted data looked like from a packet sniffer.
Your question is: Why I use HTTPS but the Packet Capture or Charles can view all of the SSL / HTTPS traffic between the client and the Internet?
Because the Packet Capture(the VPN proxy) or Charles cheated your client as an intermediary:
Your client <--> Packet Capture/Charles <--> Your target server.
So the proxy tool can view all your HTTPS content(In fact they are indeed encrypted).
Solution:
You can refer the OkHttp wiki: https://github.com/square/okhttp/wiki/HTTPS
and set a Certificate pinning for your HTTPS checking. For example:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.text);
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.certificatePinner(new CertificatePinner.Builder()
.add("drakeet.me", "sha256/gGOcYKAwzEaUfun6YdxZvFSQq/x2lF/R8UizDFofveY=")
.build())
.build();
Request request = new Request.Builder()
.url("https://drakeet.me?s=type")
.post(RequestBody.create(MediaType.parse("text"), "xxx...xxx"))
.addHeader("token", "xxx")
.build();
final Handler handler = new Handler();
client.newCall(request).enqueue(new Callback() {
#Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override public void onResponse(Call call, final Response response)
throws IOException {
final String t = response.body().string();
handler.post(new Runnable() {
#Override public void run() {
textView.setText(t);
}
});
}
});
}
}
As my above codes, I preset a certificatePinner relate to the true certificatePinner of my target server, so that if I use Packet Capture/Charles now, they will create a false certificatePinner by themselve, and OkHttp will compare the two pinning, if not equal, throw a javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
And if you close the Packet Capture/Charles, the exception dismiss and send HTTPS content successfully.
You might see, that requests are still made using "http". Use different Retrofit.Builder method baseUrl(HttpUrl):
HttpUrl httpUrl = new HttpUrl.Builder()
.host(BASE_URL)
.scheme("https").build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(httpUrl)
.build();
i am trying to establish a connection between android device and a web service using ssl with client certificate (server check as require client certificate), the certificate on the server is signed by CA (go-daddy) i also have a client certificate (*.pfx), i fallowed this tutorial
to attach the client certificate file,
i am calling the web service using ksoap2, i keep getting error 403,
but from the device (or pc) web browser its working fine (after installing the certificate)...
i don't know much about client certificate but it seems to me like the connection is not using my certificate in the correct way.
when i was testing with self sign certificate it all work well.
any ideas what i am doing wrong?
my ksoap2 kod:
public void GetUser(String user_name, String password, boolean isSchedule,
boolean writeTostatistic) throws Exception {
Log.d(GlobalUtil.TAG_LOG, "Calling GetUser() web service");
String METHOD_NAME = "GetUser";
globalUtil.user = new User();
User us = new User();
HttpsTransportSE httpTransport = new KeepAliveHttpsTransportSE(host,
port, file, timeout);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER12);
SoapObject Request = new SoapObject(NAMESPACE, METHOD_NAME);
httpTransport.debug = true;
envelope.dotNet = true;
envelope.headerOut = new Element[1];
envelope.headerOut[0] = elementHeaders;
Request.addProperty("user_name", user_name);
Request.addProperty("password", password);
Request.addProperty("isSchedule", isSchedule);
Request.addProperty("writeTostatistic", writeTostatistic);
envelope.implicitTypes = true;
envelope.setOutputSoapObject(Request); // prepare request
envelope.addMapping(NAMESPACE, "User", new User().getClass());
if (useCertificate) {
try {
((HttpsServiceConnectionSE) httpTransport
.getServiceConnection())
.setSSLSocketFactory(sSLContext);
} catch (IOException e) {
e.printStackTrace();
}
} else
allowAllSSL();
List<HeaderProperty> httpHeaders = null;
try {
httpHeaders = httpTransport.call(SOAP_ACTION + METHOD_NAME,
envelope, null);
SoapObject response = (SoapObject) envelope.getResponse();
if (response == null)
return;
us.Id = Integer.parseInt(response.getProperty("Id").toString());
if (!response.getProperty("User_Name").toString()
.equals("anyType{}"))
us.User_Name = response.getProperty("User_Name").toString();
if (!response.getProperty("Password").toString()
.equals("anyType{}"))
us.Password = response.getProperty("Password").toString();
if (!response.getProperty("USER_HEBREW_FIRSTNAME").toString()
.equals("anyType{}"))
us.USER_HEBREW_FIRSTNAME = response.getProperty(
"USER_HEBREW_FIRSTNAME").toString();
if (!response.getProperty("USER_HEBREW_LASTNAME").toString()
.equals("anyType{}"))
us.USER_HEBREW_LASTNAME = response.getProperty(
"USER_HEBREW_LASTNAME").toString();
us.Merhav = Integer.parseInt(response.getProperty("Merhav")
.toString());
us.Yaam = Integer.parseInt(response.getProperty("Yaam").toString());
us.Tat_Mifal = Integer.parseInt(response.getProperty("Tat_Mifal")
.toString());
us.Ezor = Integer.parseInt(response.getProperty("Ezor").toString());
us.EzorLahatz = Integer.parseInt(response.getProperty("EzorLahatz")
.toString());
/*
* us.PasswordExpirationDate=(Date)
* response.getProperty("PasswordExpirationDate");
*/
us.PasswordExpirationDate = User
.ParsePasswordExpirationDate((response
.getProperty("PasswordExpirationDate").toString()));
us.Password = password;
globalUtil.user = us;
SetSessionCookie(httpHeaders);
Log.d(GlobalUtil.TAG_LOG, "Finish calling GetUser() web service");
} catch (IOException | XmlPullParserException e1) {
if(e1!=null)
{
Log.e(GlobalUtil.TAG_LOG, e1.getMessage());
throw e1;
}
Log.e(GlobalUtil.TAG_LOG, "Error in Login web service.");
Log.e(GlobalUtil.TAG_LOG, "requestDump: "
+ httpTransport.requestDump);
Log.e(GlobalUtil.TAG_LOG, "responseDump: "
+ httpTransport.responseDump);
}
It is probably too late for you, but I am in a similar situation...
Older versions of Android (and pre-1.7 Java) had problem with (lack of) SNI in SSL. Supposedly this has been fixed since 2.3, but I believe somehow I have managed to mimic this bug in my 5.0 Android emulator. (Probably by using custom socket context factory.) In a browser I can access the URL using the same keystore/truststore, from Android I get 403.
What makes me believe that indeed lack of Server Name Indication is the cause...
openssl s_client -tls1_2 -connect myhost.domain.com:443 -state -cert client.crt -key client.key -pass pass:******** -CAfile server.cer -servername myhost.domain.com
...omitting the -servername param at the end results in 403, which is exactly what my Android code achieves. :D
I'm writing an Android app that connects to a cPanel server (Apache 2.2.22) page which is password protected. When the authentication credentials are correct, I have no problem connecting. However, when the credentials are incorrect, my Android application seems to freeze in the HttpURLConnection.getResponseCode() method. The logs on the server show hundreds of requests being sent from my Android device, all returning a 401 as expected, but for some reason this is not reflected in my application.
Here is my code, executed from within an AsyncTask:
#Override
protected Integer doInBackground(String... bookInfoString) {
// Stop if cancelled
if(isCancelled()){
return null;
}
Log.i(getClass().getName(), "SendToDatabase.doInBackground()");
String apiUrlString = getResources().getString(R.string.url_vages_library);
try{
NetworkConnection connection = new NetworkConnection(apiUrlString);
connection.appendPostData(bookInfoString[0]);
int responseCode = connection.getResponseCode();
Log.d(getClass().getName(), "responseCode: " + responseCode);
return responseCode;
} catch(IOException e) {
return null;
}
}
This code makes use of my own class NetworkConnection, which is just a basic wrapper class around an HttpURLConnection, to avoid repeating code. Here it is:
public class NetworkConnection {
private String url;
private HttpURLConnection connection;
public NetworkConnection(String urlString) throws IOException{
Log.i(getClass().getName(), "Building NetworkConnection for the URL \"" + urlString + "\"");
url = urlString;
// Build Connection.
try{
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(1000 /* 1 seconds */);
connection.setConnectTimeout(1000 /* 1 seconds */);
} catch (MalformedURLException e) {
// Impossible: The only two URLs used in the app are taken from string resources.
e.printStackTrace();
} catch (ProtocolException e) {
// Impossible: "GET" is a perfectly valid request method.
e.printStackTrace();
}
}
public void appendPostData(String postData) {
try{
Log.d(getClass().getName(), "appendPostData() called.\n" + postData);
Log.d(getClass().getName(), "connection.getConnectTimeout(): " + connection.getConnectTimeout());
Log.d(getClass().getName(), "connection.getReadTimeout(): " + connection.getReadTimeout());
// Modify connection settings.
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
// Get OutputStream and attach POST data.
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
writer.write(postData);
if(writer != null){
writer.flush();
writer.close();
}
} catch (SocketTimeoutException e) {
Log.w(getClass().getName(), "Connection timed out.");
} catch (ProtocolException e) {
// Impossible: "POST" is a perfectly valid request method.
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// Impossible: "UTF-8" is a perfectly valid encoding.
e.printStackTrace();
} catch (IOException e) {
// Pretty sure this is impossible but not 100%.
e.printStackTrace();
}
}
public int getResponseCode() throws IOException{
Log.i(getClass().getName(), "getResponseCode()");
int responseCode = connection.getResponseCode();
Log.i(getClass().getName(), "responseCode: " + responseCode);
return responseCode;
}
public void disconnect(){
Log.i(getClass().getName(), "disconnect()");
connection.disconnect();
}
}
And finally, here is a fraction of the logcat logs:
05-03 11:01:16.315: D/vages.library.NetworkConnection(3408): connection.getConnectTimeout(): 1000
05-03 11:01:16.315: D/vages.library.NetworkConnection(3408): connection.getReadTimeout(): 1000
05-03 11:01:16.585: I/vages.library.NetworkConnection(3408): getResponseCode()
05-03 11:04:06.395: I/vages.library.MainActivity$SendToDatabase(3408): SendToDatabase.onPostExecute(null)
You can see the the method seems to just return null after a random amount of time. The longest I have waited was exactly 15 minutes. There are also several memory logs (GC_CONCURRENT) from dalikvm between the last two info logs which I have omitted.
I should also say that at the moment I am not using https, although I do not believe that should cause any problems. I would be very grateful for any feedback with this, whether it's a complete answer or just a comment telling me what isn't the problem, as I am still unsure whether this problem is server-side or client-side.
Thank you very much,
William
EDIT: I forgot to mention before, I am attaching my authentication credentials with my own custom java.net.Authenticator:
public class CustomAuthenticator extends Authenticator {
Context mContext;
public CustomAuthenticator(Context context){
super();
mContext = context;
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
return new PasswordAuthentication(username, password.toCharArray());
}
}
which I set in the activity'sonCreate() method:
Authenticator.setDefault(new CustomAuthenticator(mContext));
Also, I have used curl to request the password protected resource, and have received a 401 as expected. I am now assuming the problem is client-side.
It seems to be an issue with using Authenticator in POST connections. It's quite old so I don't know if it still exists.
I would try two things:
Add a log line in the getPasswordAuthentication of the Authenticator to see if it's effectively called. If nothing is printed, you should check that you add the default Authenticator before it's called. You say you do it in the onCreate(), so it should be fine but it's good to be sure.
Avoid using the Authenticator (at least for testing purposes) and send the auth info directly in the HTTP Request. I usually do it this way:
String auth = user + ":" + pass;
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization",
"Basic " + Base64.encode(auth.getBytes()));
// Set other parameters and read the result...
The problem was that the 401 Unauthorized status is sent when the Authorization header is missing and when the credentials contained within the header are incorrect. Therefore, my app was constantly sending the same request over and over to no avail. I have therefore found a workaround to the problem by adding a counter into my CustomAuthenticator:
public class CustomAuthenticator extends Authenticator {
public static int RETRIES = 3;
int mRetriesLeft;
Context mContext;
public CustomAuthenticator(Context context){
super();
mRetriesLeft = RETRIES;
mContext = context;
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
Log.i(getClass().getName(), "getPasswordAuthentication() - mCounter: " + mRetriesLeft);
if(mRetriesLeft > 0){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
mRetriesLeft--;
return new PasswordAuthentication(username, password.toCharArray());
} else {
Log.w(getClass().getName(), "No more retries. Returning null");
mRetriesLeft = RETRIES;
return null;
}
}
public void reset(){
mRetriesLeft = RETRIES;
}
}
I should say however that I do not like this solution and therefore, have not accepted it. You have to remember to reset the counter whenever you make a new request (I do it in AsyncTask.onPreExecute()), or else every third request will fail. Also, I'm sure there must be a native way to do this, although after scouring the documentation I can't find it. I would still be very grateful if anyone can point it out to me.
I don't know if I am right or not but my solution has worked for me for a whole day without a glitch.
Try doing this
byte[] buf = new byte[4096];
Inputstream is;
do
{
http conn code etc;
is=conn.getInputStream();
if(is.read(buf)==0)
{
flag=1;
}
//u can either is.close(); or leave as is
//code
int serverResponseCode = connection.getResponseCode();
String serverResponseMessage = connection.getResponseMessage();
conn.disconnect();
} while(flag==1);
I have this code so far:
private class DownloadWebPageTask extends AsyncTask<String, Void, String>
{
#Override
protected String doInBackground(String... theParams)
{
String myUrl = theParams[0];
String myEmail = theParams[1];
String myPassword = theParams[2];
HttpPost post = new HttpPost(myUrl);
post.addHeader("Authorization","Basic "+ Base64.encodeToString((myEmail+":"+myPassword).getBytes(), 0 ));
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String response = null;
try
{
response = client.execute(post, responseHandler);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(
new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null)
{
response += s;
}
}
catch (Exception e)
{
e.printStackTrace();
}
return response;
}
#Override
protected void onPostExecute(String result)
{
}
}
This code does not compile because I am running into confusion at the point of:
response = client.execute(post, responseHandler);
InputStream content = execute.getEntity().getContent();
I got that code from tinkering with various examples, and not sure what Object the client is supposed to be, and whether the first line will just get me the server response, or I have to go the route of getting the InputStream and reading the server response in?
Please help me understand how to do this correctly.
Thank you!
I have managed to use Digest authentication using OkHttp. In this code sample I also use Dagger and Robospice-retrofit. What I did was creating an OkHttp Authenticator and assign it to my custom OkHttp client.
The authenticator class implements an authenticate method that will be called whenever the server encounters a 401 error and expects an Authorization header back (if it expects Proxy-Authorization you should implement the authenticateProxy method.
What it basically does is wrapping calls to the HttpClient DigestScheme and make it usable for OkHttp. Currently it does not increase the nc counter. This could cause problems with your server as it could be interpreted as a replay attack.
public class DigestAuthenticator implements com.squareup.okhttp.Authenticator {
#Inject DigestScheme mDigestScheme;
#Inject org.apache.http.auth.Credentials mCredentials;
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
String authHeader = buildAuthorizationHeader(response);
if (authHeader == null) {
return null;
}
return response.request().newBuilder().addHeader("Authorization", authHeader).build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
private String buildAuthorizationHeader(Response response) throws IOException {
processChallenge("WWW-Authenticate", response.header("WWW-Authenticate"));
return generateDigestHeader(response);
}
private void processChallenge(String headerName, String headerValue) {
try {
mDigestScheme.processChallenge(new BasicHeader(headerName, headerValue));
} catch (MalformedChallengeException e) {
Timber.e(e, "Error processing header " + headerName + " for DIGEST authentication.");
}
}
private String generateDigestHeader(Response response) throws IOException {
org.apache.http.HttpRequest request = new BasicHttpRequest(
response.request().method(),
response.request().uri().toString()
);
try {
return mDigestScheme.authenticate(mCredentials, request).getValue();
} catch (AuthenticationException e) {
Timber.e(e, "Error generating DIGEST auth header.");
return null;
}
}
}
The authenticator will then be used in an OkHttpClient built with a provider:
public class CustomClientProvider implements Client.Provider {
#Inject DigestAuthenticator mDigestAuthenticator;
#Override
public Client get() {
OkHttpClient client = new OkHttpClient();
client.setAuthenticator(mDigestAuthenticator);
return new OkClient(client);
}
}
Finally the client is set to the RetrofitRobospice server in the function createRestAdapterBuilder:
public class ApiRetrofitSpiceService extends RetrofitJackson2SpiceService {
#Inject Client.Provider mClientProvider;
#Override
public void onCreate() {
App.get(this).inject(this);
super.onCreate();
addRetrofitInterface(NotificationRestInterface.class);
}
#Override
protected String getServerUrl() {
return Constants.Url.BASE;
}
#Override
protected RestAdapter.Builder createRestAdapterBuilder() {
return super.createRestAdapterBuilder()
.setClient(mClientProvider.get());
}
}
You might want to switch to HttpURLConnection. According to this article its API is simpler than HttpClient's and it's better supported on Android. If you do choose to go with HttpURLConnection, authenticating is pretty simple:
Authenticator.setDefault(new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password".toCharArray());
}
});
After that, continue using HttpURLConnection as usual. A simple example:
final URL url = new URL("http://example.com/");
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
final InputStream is = conn.getInputStream();
final byte[] buffer = new byte[8196];
int readCount;
final StringBuilder builder = new StringBuilder();
while ((readCount = is.read(buffer)) > -1) {
builder.append(new String(buffer, 0, readCount));
}
final String response = builder.toString();
The version of Apache's HttpClient shipped with Android is based on an old, pre-BETA version of HttpClient. Google has long recommended against using it and removed it in Android 6.0. Google's replacement HttpURLConnection does not support HTTP digest authentication, only basic.
This leaves you with a few options, including:
Migrate to HttpURLConnection (as Google recommends) and use a library, bare-bones-digest, for digest authentication. Example below.
Use the OkHttp library instead of HttpURLConnection or HttpClient. OkHttp does not support digest out of the box, but there's a library okhttp-digest that implements a digest authenticator. Example below.
Continue using the (deprecated) HttpClient by explicitly adding the 'org.apache.http.legacy' library to your build, as mentioned in the changelist for Android 6.0.
There is an Apache project for porting newer versions of HttpClient to Android, but the project has been discontinued. Read more on Apache's page on HttpClient for Android.
Implement HTTP digest yourself.
Here is a verbose example of how to authenticate a request using bare-bones-digest and HttpURLConnection (copied from the project's github page):
// Step 1. Create the connection
URL url = new URL("http://httpbin.org/digest-auth/auth/user/passwd");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Step 2. Make the request and check to see if the response contains
// an authorization challenge
if (connection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Step 3. Create a authentication object from the challenge...
DigestAuthentication auth = DigestAuthentication.fromResponse(connection);
// ...with correct credentials
auth.username("user").password("passwd");
// Step 4 (Optional). Check if the challenge was a digest
// challenge of a supported type
if (!auth.canRespond()) {
// No digest challenge or a challenge of an unsupported
// type - do something else or fail
return;
}
// Step 5. Create a new connection, identical to the original
// one..
connection = (HttpURLConnection) url.openConnection();
// ...and set the Authorization header on the request, with the
// challenge response
connection.setRequestProperty(
DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
auth.getAuthorizationForRequest("GET", connection.getURL().getPath()));
}
Here is an example using OkHttp and okhttp-digest (copied from the okhttp-digest page):
client = new OkHttpClient();
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials("username", "pass"));
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
client.interceptors().add(new AuthenticationCacheInterceptor(authCache));
client.setAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache));
Request request = new Request.Builder()
.url(url);
.get()
.build();
Response response = client.newCall(request).execute();