5

I'm trying to port an Android app to IOS platform. I get data from a service located in an Apache Tomcat server and recently they added security on the service. The security overview is

  1. https simple request
  2. if(valid_user)->service returns data
  3. else->service returns login form ->post credentials->service returns data

The Android version work without problem. Before security was added I was performing simple http request but now I need to do a POST request with my credentials and I allways receive error.html as result. I'm far from being expert on network programming.

This is the Android http client setup code:

if(httpClient==null)
{
 DefaultHttpClient client = new DefaultHttpClient();

 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
 trustStore.load(null, null);
 SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
 sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
 SchemeRegistry registry = new SchemeRegistry();
 registry.register(new Scheme("https", sf, 8443));

 mgr = new ThreadSafeClientConnManager(client.getParams(), registry);
 int timeoutConnection = 5000;
 HttpConnectionParams.setConnectionTimeout(client.getParams(), timeoutConnection);
 // Set the default socket timeout (SO_TIMEOUT) 
 // in milliseconds which is the timeout for waiting for data.
 int timeoutSocket = 7000;
 HttpConnectionParams.setSoTimeout(client.getParams(), timeoutSocket);
 httpClient = new DefaultHttpClient(mgr, client.getParams());
}

And this is MySSLSocketFactory class:

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

This is the POST request code for Android (it is working OK):

...
HttpPost httpost = new HttpPost(host+"/serviceName/j_security_check"); 
List<BasicNameValuePair> nvps = new ArrayList<BasicNameValuePair>();
nvps.add(new BasicNameValuePair("j_username", userName));
nvps.add(new BasicNameValuePair("j_password", userPassword));

try {
     httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
    } 
catch (UnsupportedEncodingException e) {
    // TODO Auto-generated catch block
                e.printStackTrace();
}

try {
     httpResponse = httpClient.execute(httpost);
} catch (ClientProtocolException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
} catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
}
entity = httpResponse.getEntity();
...

I have tried several ways but allways with the same result. I only add code that I think is remarkable:

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
  NSURLCredential *credential=[NSURLCredential credentialWithUser:self.userName password:self.userpassword persistence:NSURLCredentialPersistenceForSession];
   [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}

In the method where the POST request is defined:

{
...
NSString *urlstr=[NSString stringWithFormat:@"%@%@",host,@"serviceName/j_security_check"];
NSURL *url=[[NSURL alloc]initWithString:urlstr];
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url];
NSString *params=[[NSString alloc]initWithFormat:@"j_username=my_userName&j_userpassword=my_userPassword"];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
[[NSURLConnection alloc]initWithRequest:request delegate:self];
}

I have been searching for a valid POST request on the web but none has been usefull...

If you need more info please ask for it

Your help is apreciated.

EDIT: I changed willSendRequestForAuthenticationChallenge delegate because with the previous one I don't get the login.html form.

EDIT: I added http client setup code from android. Now the method posting data looks similar to @MTahir answer but I still get the error.html, which means (I think) that the POST method is working ok but the data isn't encripted the correct way.

  • i have some doubt... whether you need to send post request or direct get methods... please specify... and you need any encoded value... – maheswaran May 27 '13 at 10:13
  • @maheswaran I use GET methods for common data accessing (whenever my user has a valid session on the server) but I think POST method is a requirement in user validation, since in the Android version seems to use POST. All comunication is https, if you want I can add httpclient configuration code from the Android version. – Pedro Areizaga May 27 '13 at 13:03
  • I added more info in the question. – Pedro Areizaga May 28 '13 at 08:21

1 Answers1

3

If you are looking for a working piece of code that posts data, then try this, I have using this, it works great.

It takes the data to be sent in dictionary object. Ecodes the data to be sent as POST and then returns the response (if you want the results in string format you can use [[NSString alloc] initWithData:dresponse encoding: NSASCIIStringEncoding]; when returning data)

-(NSData*) postData:(NSDictionary *) postDataDic{

NSData *dresponse = [[NSData alloc] init];

NSURL *nurl = [NSURL URLWithString:url];
NSDictionary *postDict = [[NSDictionary alloc] initWithDictionary:postDataDic];
NSData *postData = [self encodeDictionary:postDict];

// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:nurl];
[request setHTTPMethod:@"POST"]; // define the method type
[request setValue:[NSString stringWithFormat:@"%d", postData.length] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

    // Peform the request
    NSURLResponse *response;
    NSError *error = nil;

    dresponse = [NSURLConnection sendSynchronousRequest:request  
                                                 returningResponse:&response
                                                             error:&error];

return dresponse;
}

This method prepares the Dictionary data for POST

  - (NSData*)encodeDictionary:(NSDictionary*)dictionary {
    NSMutableArray *parts = [[NSMutableArray alloc] init];
    for (NSString *key in dictionary) {
        NSString *encodedValue = [[dictionary objectForKey:key] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSString *encodedKey = [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 
        NSString *part = [NSString stringWithFormat: @"%@=%@", encodedKey, encodedValue];
        [parts addObject:part];
    }
    NSString *encodedDictionary = [parts componentsJoinedByString:@"&"];
    return [encodedDictionary dataUsingEncoding:NSUTF8StringEncoding];
}

If you have any confusion do let me know.

MTahir
  • 1,173
  • 1
  • 13
  • 25
  • I used your code example and I get the error.html from the server. I think it's a encryption error. I think that the post method is performed ok, but the data in the post needs some specific encription. Thanks for your time. – Pedro Areizaga May 28 '13 at 08:20
  • do you have access to the web service script and if yes, try logging the received values from iOS code, may be it will help – MTahir May 28 '13 at 08:57
  • Now I don't have complete access to the service code, for example ServiceRealm.jar (authoritation responsible) is in binary format, it's not included with Service project. – Pedro Areizaga May 28 '13 at 16:27
  • I have used the above code only for http, not https, this [link](http://stackoverflow.com/questions/933331/how-to-use-nsurlconnection-to-connect-with-ssl-for-an-untrusted-cert/2033823#2033823) might help you out – MTahir May 28 '13 at 19:40
  • 1
    Now it is working! I changed the avobe line: `[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];` and also I made the request asynchronous with this code `NSURLConnection *con=[[NSURLConnection alloc]initWithRequest:request delegate:self]; [con start];`. I also followed the link you give me and added those methods to the class. Thank you!Eskerrik asko!(thanks in my native language, euskera) And sorry for my poor english! – Pedro Areizaga May 29 '13 at 07:12