I am trying to implement the Digest auth in android. I read many posts and they say HttpUrlConnection class does not support the Digest auth. However, I have implemented it using bare-bones-digest library. Now it is working fine but the API call became very slow. It is taking double time to load the data as compared to it was taking with basic auth. Using base-bones-digest they say to avoid sending each request twice, in subsequent requests the client can reuse the challenge. Only the first request will have to be sent twice. But no implementation is given for that.
HttpURLConnection httpURLConnection = null;
try {
if(mAuthorizationString != null && !mAuthorizationString.equals("")){
URL url = new URL(apiEndpoint);
httpURLConnection = (HttpURLConnection) url.openConnection();
DigestAuthentication auth = DigestAuthentication.fromResponse(httpURLConnection);
// ...with correct credentials
auth.username("username").password("password");
httpURLConnection.setRequestProperty(DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
mAuthorizationString);
}
else{
URL url = new URL(apiEndpoint);
httpURLConnection = (HttpURLConnection) url.openConnection();
// Step 2. Make the request and check to see if the response contains an authorization challenge
if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Step 3. Create an authentication object from the challenge...
DigestAuthentication auth = DigestAuthentication.fromResponse(httpURLConnection);
// ...with correct credentials
auth.username("username").password("password");
// 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 httpURLConnection;
}
// Step 5. Create a new connection, identical to the original one...
httpURLConnection = (HttpURLConnection) url.openConnection();
mAuthorizationString = auth.getAuthorizationForRequest(requestMethod, httpURLConnection.getURL().getPath());
// ...and set the Authorization header on the request, with the challenge response
httpURLConnection.addRequestProperty(DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
mAuthorizationString);
}
}
} catch (IOException e) {
e.printStackTrace();
}
I have fixed it Myself by updating the code as follows.
HttpURLConnection httpURLConnection = null;
try {
URL url = new URL(apiEndpoint);
httpURLConnection = (HttpURLConnection) url.openConnection();
if(sAuthorizationString != null && !sAuthorizationString.equals("")){
httpURLConnection.addRequestProperty(DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
sAuthorizationString);
}
else{
// Step 2. Make the request and check to see if the response contains an authorization challenge
if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Step 3. Create an authentication object from the challenge...
DigestAuthentication auth = DigestAuthentication.fromResponse(httpURLConnection);
// ...with correct credentials
auth.username("username").password("password");
// 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 httpURLConnection;
}
// Step 5. Create a new connection, identical to the original one...
httpURLConnection = (HttpURLConnection) url.openConnection();
sAuthorizationString = auth.getAuthorizationForRequest(requestMethod, httpURLConnection.getURL().getPath());
// ...and set the Authorization header on the request, with the challenge response
httpURLConnection.addRequestProperty(DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
sAuthorizationString);
}
}
} catch (IOException e) {
e.printStackTrace();
}
Related
I am trying to develop a part of my app in which i want to get video details for a keyword. I am using the search list API. I think I have an issue with the authorization. I am getting a 401. I have tried passing my authorization details.
I have tried the following code after going through a few resources online and I am current getting the java.io.FileNotFoundException at the line at which i get the Input Stream. The connection code that I am getting is a 401.
The given code is in the doInBackground of an AsyncTask.
String ytres="";
URL url;
HttpURLConnection urlConnection = null;
try {
Log.d("youtubedata","a1");
url = new URL("https://www.googleapis.com/youtube/v3/search?part=id&q=queen%20bohemian");
Log.d("youtubedata","a2");
Log.d("youtubedata","a");
urlConnection = (HttpURLConnection) url
.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setRequestProperty ("Authorization", "Bearer MYAPIKEY");
urlConnection.setRequestProperty ("Accept", "application/json");
Log.d("youtubedatanum",Integer.toString(urlConnection.getResponseCode()));
InputStream in = urlConnection.getInputStream();
InputStreamReader isw = new InputStreamReader(in);
int data = isw.read();
while (data != -1) {
char current = (char) data;
data = isw.read();
ytres=ytres+current;
System.out.print(current);
}
Log.d("youtubedata",ytres);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return ytres;
I do suspect the issue is in the way i have passed my API Key.
Please go through the API docs at Calling the API and at Search Endpoint.
You'll see that your URL does not contain the needed API key passed on as the key parameter.
It may also worth it to dug up Google's own sample Java implementation at Search.java. However, that code, due to the layer of library abstraction it uses, is not of immediate help since it obscures the way a client should work directly with the API.
For You-tube Data API error 401
Possible issue for unauthorized (401) can be
authorizationRequired
youtubeSignupRequired
Check You-tube Data API error doc Here.
Also confirm that you have follow THIS steps to integrate API.
Sample API Code you can find Here
Hope this can help you.
I have ASP.Net MVC4 WEB API, hosted in local IIS.
I request the api from android using GET method. Response is 405 Action not Allowed.
I Have This Method in Controller :
public IEnumerable<Table> GET()
{
return _repository.GetAll();
}
But When I Change the Method to POST:
public IEnumerable<Table> POST()
{
return _repository.GetAll();
}
and request from android with POST method.
I got the RESULTS.
I request both GET and POST with the same route.
'/api/tables'
In android project, I use HttpUrlConnection to request API.
try{
URL url = new URL(urls[0]);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000 /* milliseconds */);
conn.setConnectTimeout(7500 /* milliseconds */);
conn.setRequestProperty("Accept", "application/json");
conn.setRequestMethod(method);
conn.setDoInput(true);
if(method == "POST")
conn.setDoOutput(true);
if(params != null){
// Get OutputStream for the connection and
// write the parameter query string to it
OutputStream os = conn.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(getURLEncodedString(params));
writer.flush();
writer.close();
}
// Starts the query
conn.connect();
What am i doing wrong?
info:
when request from browser Get Method return results and Post Method 405.
The problem is in my android project
Android Developer: HttpUrlConnection
HttpURLConnection uses the GET method by default. It will use POST if
setDoOutput(true) has been called.
stackoverflow: https://stackoverflow.com/questions/8187188/android-4-0-ics-turning-httpurlconnection-get-requests-into-post-requests
May be it's a routing issue. Make some little changes in your WebApi.config of webapi
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
One more important thing here is that with this style of routing, you must use attributes to specify the allowed HTTP methods (like [HttpGet]).
First post so forgive me if I forgot some rules :P
I'm developing an android app that requires basic to and fro data transfer to an app on google app engine. That app is written in python and I'm using the flask framework to communicate with the app (through http request, insecure and everything else I know but I just want a proof of concept more than anything else at the moment).
My problem is that when the python app is deployed on gae and I send a request from the android app on a physical device, I get no response, yet if I put the necessary URL in the browser, I get a response no problem.
Anyway the java function code:
private boolean doLogin(String username, String passwd){
boolean loggedIn = false;
try {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL url = new URL("http://appname.appspot.com/login/admin/admin");// + username + "/" + passwd+"/");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//con.setRequestMethod("POST");
InputStream in = new BufferedInputStream(con.getInputStream());
if (readStream(in) == true){
loggedIn = true;
}
//con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return loggedIn;
}
And the python function:
#webapp.route('/login/<username>/<passwd>', methods=["GET", "POST"])
def dologin(username, passwd):
def validusertype(u2check, p2check):
if db.GqlQuery("SELECT * FROM UserAccount WHERE username = :u2check AND password = :passwd", u2check = u2check, passwd=p2check).count() > 0:
qry = db.GqlQuery("SELECT * FROM UserAccount WHERE username = :u2check", u2check = u2check)
for r in qry.fetch(limit=None):
return r.usertype
return None
if request.method == "GET":
_type = validusertype(username, passwd)
if _type:
return "True"
# elif request.method == "POST":
# Fall though - if nothing above takes, we end up here.
return "False"
Any help at all would be greatly appreciated! Also the python code works, Copy/paste operation kinda screwed it up a bit.
Update
I figured out what was wrong. I was missing a few essential pieces to the puzzle but thanks to #hgoebl for pointing out my errors (ie the whole function, ow my ego). So here's the replacement code for anyone that may need it.
try {
URL url = new URL("http://app.appspot.com/login/admin/admin");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = readStream(is);
return contentAsString;
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (is != null) {
is.close();
}
}
Also to put these few lines in onCreate():
StrictMode.ThreadPolicy policy = new StrictMode.
ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
I need to reliably detect if a device has full internet access, i.e. that the user is not confined to a captive portal (also called walled garden), i.e. a limited subnet which forces users to submit their credentials on a form in order to get full access.
My app is automating the authentication process, and therefore it is important to know that full internet access is not available before starting the logon activity.
The question is not about how to check that the network interface is up and in a connected state. It is about making sure the device has unrestricted internet access as opposed to a sandboxed intranet segment.
All the approaches I have tried so far are failing, because connecting to any well-known host would not throw an exception but return a valid HTTP 200 response code because all requests are routed to the login page.
Here are all the approaches I tried but they all return true instead of false for the reasons explained above:
1:
InetAddress.getByName(host).isReachable(TIMEOUT_IN_MILLISECONDS);
isConnected = true; <exception not thrown>
2:
Socket socket = new Socket();
SocketAddress sockaddr = new InetSocketAddress(InetAddress.getByName(host), 80);
socket.connect(sockaddr, pingTimeout);
isConnected = socket.isConnected();
3:
URL url = new URL(hostUrl));
URLConnection urlConn = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) urlConn;
httpConn.setAllowUserInteraction(false);
httpConn.setRequestMethod("GET");
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;
So, how do I make sure I connected to an actual host instead of the login redirection page? Obviously, I could check the actual response body from the 'ping' host I use but it does not look like a proper solution.
For reference, here is the 'official' method from the Android 4.0.1 AOSP code base:
WifiWatchdogStateMachine.isWalledGardenConnection(). I am including the code below just in case the link breaks in the future.
private static final String mWalledGardenUrl = "http://clients3.google.com/generate_204";
private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;
private boolean isWalledGardenConnection() {
HttpURLConnection urlConnection = null;
try {
URL url = new URL(mWalledGardenUrl); // "http://clients3.google.com/generate_204"
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
urlConnection.getInputStream();
// We got a valid response, but not from the real google
return urlConnection.getResponseCode() != 204;
} catch (IOException e) {
if (DBG) {
log("Walled garden check - probably not a portal: exception "
+ e);
}
return false;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
This approach relies on a specific URL, mWalledGardenUrl = "http://clients3.google.com/generate_204" always returning a 204 response code. This will work even if DNS has been interfered with since in that case a 200 code will be returned instead of the expected 204. I have seen some captive portals spoofing requests to this specific URL in order to prevent the Internet not accessible message on Android devices.
Google has a variation of this theme: fetching http://www.google.com/blank.html will return a 200 code with a zero-length response body. So if you get a non-empty body this would be another way to figure out that you are behind a walled garden.
Apple has its own URLs for detecting captive portals: when network is up IOS and MacOS devices would connect to an URL like http://www.apple.com/library/test/success.html, http://attwifi.apple.com/library/test/success.html, or http://captive.apple.com/hotspot-detect.html which must return an HTTP status code of 200 and a body containing Success.
NOTE:
This approach will not work in areas with regionally restricted Internet access such as China where the whole country is a walled garden, and where some Google/Apple services might be blocked. Some of these might not be blocked: http://www.google.cn/generate_204, http://g.cn/generate_204, http://gstatic.com/generate_204 or http://connectivitycheck.gstatic.com/generate_204 — yet these all belong to google so not guaranteed to work.
Another possible solution might be to connect via HTTPS and inspect the target certificate. Not sure if walled gardens actually serve the login page via HTTPS or just drop the connections. In either case, you should be able to see that your destination is not the one you expected.
Of course, you also have the overhead of TLS and certificate checks. Such is the price of authenticated connections, unfortunately.
I believe preventing redirection for your connection will work.
URL url = new URL(hostUrl));
HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();
/* This line prevents redirects */
httpConn.setInstanceFollowRedirects( false );
httpConn.setAllowUserInteraction( false );
httpConn.setRequestMethod( "GET" );
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;
If that doesn't work, then I think the only way to do it is to check the body of the response.
This has been implemented on Android 4.2.2+ version - I find their approach fast and interesting :
CaptivePortalTracker.java detects walled garden as follows
- Try to connect to www.google.com/generate_204
- Check that the HTTP response is 204
If the check fails, we are in a walled garden.
private boolean isCaptivePortal(InetAddress server) {
HttpURLConnection urlConnection = null;
if (!mIsCaptivePortalCheckEnabled) return false;
mUrl = "http://" + server.getHostAddress() + "/generate_204";
if (DBG) log("Checking " + mUrl);
try {
URL url = new URL(mUrl);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
urlConnection.getInputStream();
// we got a valid response, but not from the real google
return urlConnection.getResponseCode() != 204;
} catch (IOException e) {
if (DBG) log("Probably not a portal: exception " + e);
return false;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
if you are already using retrofit you can do it by retrofit. just make a ping.html page and send an head request to it using retrofit and make sure your http client is configured like below: (followRedirects(false) part is the most important part)
private OkHttpClient getCheckInternetOkHttpClient() {
return new OkHttpClient.Builder()
.readTimeout(2L, TimeUnit.SECONDS)
.connectTimeout(2L, TimeUnit.SECONDS)
.followRedirects(false)
.build();
}
then build your retrofit like below:
private InternetCheckApi getCheckInternetRetrofitApi() {
return (new Retrofit.Builder())
.baseUrl("[base url of your ping.html page]")
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.client(getCheckInternetOkHttpClient())
.build().create(InternetCheckApi.class);
}
your InternetCheckApi.class would be like:
public interface InternetCheckApi {
#Headers({"Content-Typel: application/json"})
#HEAD("ping.html")
Call<Void> checkInternetConnectivity();
}
then you can use it like below:
getCheckInternetOkHttpClient().checkInternetConnectivity().enqueue(new Callback<Void>() {
public void onResponse(Call<Void> call, Response<Void> response) {
if(response.code() == 200) {
//internet is available
} else {
//internet is not available
}
}
public void onFailure(Call<Void> call, Throwable t) {
//internet is not available
}
}
);
note that your internet check http client must be separate from your main http client.
This is best done here as in AOSP :
https://github.com/aosp-mirror/platform_frameworks_base/blob/6bebb8418ceecf44d2af40033870f3aabacfe36e/core/java/android/net/captiveportal/CaptivePortalProbeResult.java#L61
https://github.com/aosp-mirror/platform_frameworks_base/blob/e3a0f42e8e8678f6d90ddf104d485858fbb2e35b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
private static final String GOOGLE_PING_URL = "http://google.com/generate_204";
private static final int SOCKET_TIMEOUT_MS = 10000;
public boolean isCaptivePortal () {
try {
URL url = new URL(GOOGLE_PING_URL);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
urlConnection.getInputStream();
return (urlConnection.getResponseCode() != 204)
&& (urlConnection.getResponseCode() >= 200)
&& (urlConnection.getResponseCode() <= 399);
} catch (Exception e) {
// for any exception throw an exception saying check was unsuccesful
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
Please note this will probably not work on a proxy network and something more advanced as in the AOSP url needs to be done
I'm using HttpURLConnection to do communication with a backend server and im doing so in an async task in the doInBackground method as you should.
Now I need to be able to follow 302 redirects, but I'm having some problems with this. The issue is that the new location usually will be on another host, however when doing the redirect request it seem not to change the URL to a new host hence I get a 404 error saying the specified path does not exits.
Now I know I could set HtppURLConnection.setFollowRedirect but I need to have more control over the redirects so they should not just be followed blindly. The Redirect behavour should be controlled by the object who called the asynctask (when an asynctask object is created you pass the object who creates it in a parameter called _callback).
Heres's my current code:
protected HttpResponse doInBackground(String... req) {
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) this._url.openConnection();
urlConnection.setConnectTimeout( (int) this._timeout*1000);
String body = req[0];
// set headers / write information to output stream if request is post
// create the response object
HttpResponse responseObject = null;
try
{
// get status, contenttype, charset...
InputStream in = null;
if (urlConnection.getResponseCode() != -1 && urlConnection.getResponseCode() < 300)
{
in = new BufferedInputStream(urlConnection.getInputStream(), 8192);
}
else
{
in = new BufferedInputStream(urlConnection.getErrorStream(), 8192);
}
responseObject = new HttpResponse(in, status, contentType, charset);
// if redirect
if (status == 302 && this._callback.onRedirect(responseObject) == true)
{
// recall
String url = urlConnection.getHeaderField("location");
Log.v("Async Task", "Redirect location: " + url);
this._url = null;
this._url = new URL(url);
urlConnection.disconnect();
urlConnection = null;
responseObject = this.doInBackground(req);
}
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
// return the response
return responseObject;
}
// catch some other exceptions
finally
{
if (urlConnection != null)
{
urlConnection.disconnect();
} }
}
And as said the problem is that the redirect request seem to change the path of the URL but not the host. The URL object itself seem to contain the right information so I have no idea why this is happening. (I'm getting HTML as response which is an 404 error page that includes the server name of the old server)
Thanks for any help!
Note: HttpResponse is just an object I created for holding the relevant information about the response.
This was caused by the fact that I sent the same headers and did not change the "host" header of the request which caused Apache to be confused it seems.