问题
I'm writing an Android app which sends data in JSON format to a Django REST API running on a local server. It is an https connection to the server and all necessary certificates are integratted into the app. Bevore we've updated to Django 1.6 we were running on Django 1.4 and everything worked fine. Now with Django 1.6 the app is still able to get the CSRF token, to log in and to do a HttpGet request, which returns the expected JSON object. Only the HttpPost requests are rejected with 403 Forbidden. I searched through the web and only found out that the Cross Site Request Forgery protection could be a problem, but we are accessing the API via webbrowser and app, so I think it would be a security lack to turn it off.
Here is what I do to start httpPost and HttpGet:
After starting the app a HttpClient holding the server certificates is created, this one is than used for all get and post requests:
private DefaultHttpClient createHttpClient(Context ctx) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException
{
SSLSocketFactory ssf = new SSLSocketFactory(keyStore);
SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 888));
// https scheme
schemeRegistry.register(new Scheme("https", ssf, 443));
HttpParams params = new BasicHttpParams();
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, true);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "utf-8");
//ClientConnectionManager cm =
// new ThreadSafeClientConnManager(params, schemeRegistry);
ClientConnectionManager cm =
new SingleClientConnManager(params, schemeRegistry);
return new DefaultHttpClient(cm, params);
}
Then I send a get request to get the CSRF token and login afterwards:
public static String openAuthStream(DefaultHttpClient httpClient, String u, String pw) throws ClientProtocolException, IOException
{
// defaultHttpClient
HttpGet httpGet = new HttpGet(Constants.LOGIN_URL);
httpClient.execute(httpGet);
String csrfToken = new String();
CookieStore cookieStore = httpClient.getCookieStore();
List <Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie: cookies) {
if (cookie.getDomain().equals(Constants.CSRF_DOMAIN) && cookie.getName().equals("csrftoken")) {
csrfToken = cookie.getValue();
}
}
httpGet.setHeader("Referer", Constants.LOGIN_URL);
httpGet.setHeader("X-CSRFToken", csrfToken);
//httpGet.abort();
List<NameValuePair> credentials = new ArrayList<NameValuePair>();
credentials.add(new BasicNameValuePair("username", u));
credentials.add(new BasicNameValuePair("password", pw));
HttpPost post = new HttpPost(Constants.LOGIN_URL);
post.setHeader("Referer", Constants.LOGIN_URL);
post.setHeader("X-CSRFToken", csrfToken);
post.setEntity(new UrlEncodedFormEntity(credentials));
HttpResponse response = httpClient.execute(post);
//post.abort();
if(response.getStatusLine().getStatusCode() == 200)
return csrfToken;
return null;
}
Finally the post requests is build up in this way:
public HttpResponse sendJSONToUrl(DefaultHttpClient httpClient,String url, String csrfToken, JSONObject json) {
try {
HttpPost httpPostJson = new HttpPost(url);
httpPostJson.setEntity(new StringEntity(json.toString(),HTTP.UTF_8));
httpPostJson.setHeader("Referer", url);
httpPostJson.setHeader("X-CSRFToken", csrfToken);
httpPostJson.setHeader("Accept", "application/json");
httpPostJson.setHeader("Content-type", "application/json");
HttpResponse response = httpClient.execute(httpPostJson);
httpPostJson.abort();
return response;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Everything worked fine on Django 1.4 and now I'm helpless. Thanks in advance!
回答1:
This is not Cross Site Request Forgery protection problem. You should pass right ContentType header for POST request. Django hadn't validated it before version 1.5, but since 1.5 it does that check - http://django.readthedocs.org/en/latest/releases/1.5.html#non-form-data-in-http-requests.
So you need to change ContentType header for your request:
httpPostJson.setHeader("Content-Type", "application/x-www-form-urlencoded");
回答2:
Solved the problem. The CSRF token changes after sucessfull login ( what not happend with Django 1.4 ), so after POST with login information another GET has to be made to get the new CSRF token and then everything works fine like before. thanks for all the help!
来源:https://stackoverflow.com/questions/24927731/android-http-post-request-returns-403-after-django-update-to-1-6