如果你自己想做一个客户端玩玩,但是又不想去搭建后台服务器,显然Bmob移动后端云是你的最佳选择。官方地址见bmob,文档地址见http://www.bmob.cn/docs。他提供了Android的sdk,同样也提供了Restful Api,但是个人建议Restful Api还是不适合直接在客户端使用,毕竟会暴露一下一些key的信息,但是本篇文章就是在android中使用它的restful api,原因嘛很简单,我想网络层自己控制,不想用它提供的android sdk,对于安全方面,同样给出了这种情况的解决方法。

新建应用

首先你得有个账号,然后你得有个应用,具体内容见http://docs.bmob.cn/restful/faststart/index.html?menukey=fast_start&key=start_restful

编写代码

我们使用OkHttp,还需要用到Gson,增加依赖

compile 'com.squareup.okhttp:okhttp:2.5.0'
    compile 'com.google.code.gson:gson:2.3.1'

增加网络访问权限

<uses-permission android:name="android.permission.INTERNET"/>

编写一个网络的请求,插入一条数据

实体类

public class Person {
    private String name;
    private String address;
    private int age;

    public Person(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
    public Person() {
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                '}';
    }
}

进行请求,这部分代码是java平台的,在android上你需要开启一个线程。

private static final String URL_INSERT ="https://api.bmob.cn/1/classes/Person";
private static final String APPLICATION_ID="8dcb9fee2f******14ab19e7dfd9d";
private static final String API_KEY="aebe3b71c9b2***********430ac2de560b1";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final Gson gson=new Gson();
private static final OkHttpClient client=new OkHttpClient();
Person person=new Person("张三","杭州",20);
RequestBody body = RequestBody.create(JSON, gson.toJson(person));
Request insert=new Request.Builder()
        .url(URL_INSERT)
        .addHeader("Content-Type","application/json")
        .addHeader("X-Bmob-Application-Id", APPLICATION_ID)
        .addHeader("X-Bmob-REST-API-Key",API_KEY)
        .post(body)
        .build();
try {
    Response execute = client.newCall(insert).execute();
    System.out.println(execute.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

注意请求头里面的几个参数,必须设置。

这时候如果你进行请求,你会发现会报一个异常

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

原来是https请求,我们需要获得证书。

当然这时候你有两个选择,一个是信任所有证书。

public class MyX509TrustManager  implements X509TrustManager {

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

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

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

try {  
        // 创建SSLContext对象,并使用我们指定的信任管理器初始化
    TrustManager[] tm = { new MyX509TrustManager() };
    SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    sslContext.init(null, tm, new java.security.SecureRandom());
    // 从上述SSLContext对象中得到SSLSocketFactory对象
    SSLSocketFactory ssf = sslContext.getSocketFactory();
    client.setSslSocketFactory(ssf);
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
} catch (NoSuchProviderException e) {
    e.printStackTrace();
} catch (KeyManagementException e) {
    e.printStackTrace();
}
public class MyX509TrustManager  implements X509TrustManager {

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

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

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

try {  
        // 创建SSLContext对象,并使用我们指定的信任管理器初始化
    TrustManager[] tm = { new MyX509TrustManager() };
    SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    sslContext.init(null, tm, new java.security.SecureRandom());
    // 从上述SSLContext对象中得到SSLSocketFactory对象
    SSLSocketFactory ssf = sslContext.getSocketFactory();
    client.setSslSocketFactory(ssf);
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
} catch (NoSuchProviderException e) {
    e.printStackTrace();
} catch (KeyManagementException e) {
    e.printStackTrace();
}

但是这种方法有安全隐患,我们还是使用证书吧。

首先用Chrome打开https://api.bmob.cn/,然后点击链接左边的锁的图形,切到连接项,点击证书信息,如下图

android response获取内容 android restful api_bmob

然后将证书导出即可,之后一直下一步即可。

android response获取内容 android restful api_Restful_02

这里假设导出的证书名字为bmob.cer,将其放到assets目录下。

然后编写一个https验证的工具类。

/**
 * User:lizhangqu(513163535@qq.com)
 * Date:2015-09-23
 * Time: 17:45
 */
public class SSLUtil {
    //使用命令keytool -printcert -rfc -file srca.cer 导出证书为字符串,然后将字符串转换为输入流,如果使用的是okhttp可以直接使用new Buffer().writeUtf8(s).inputStream()

    /**
     * 返回SSLSocketFactory
     *
     * @param certificates 证书的输入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
        return getSSLSocketFactory(null,certificates);
    }


    /**
     * 双向认证
     * @param keyManagers KeyManager[]
     * @param certificates 证书的输入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(KeyManager[] keyManagers, InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            return socketFactory;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 获得双向认证所需的参数
     * @param bks bks证书的输入流
     * @param keystorePass 秘钥
     * @return KeyManager[]对象
     */
    public static KeyManager[] getKeyManagers(InputStream bks, String keystorePass) {
        KeyStore clientKeyStore = null;
        try {
            clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bks, keystorePass.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, keystorePass.toCharArray());
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            return keyManagers;
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

如果你不想使用文件,则你可以导出证书的内容,使用命令进行导出

keytool -printcert -rfc -file bmob.cer

android response获取内容 android restful api_jni_03

然后将其赋值给一个字符串

private static final String CERT="-----BEGIN CERTIFICATE-----\n" +
            "MIIGLjCCBRagAwIBAgIDFCb6MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJJTDEWMBQGA1UE\n" +
            "ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln\n" +
            "bmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2\n" +
            "ZXIgQ0EwHhcNMTQxMTA3MDExNzM0WhcNMTUxMTA4MDIxMDQ2WjBJMQswCQYDVQQGEwJDTjEUMBIG\n" +
            "A1UEAxMLYXBpLmJtb2IuY24xJDAiBgkqhkiG9w0BCQEWFWhlc2hhb3l1ZUBmb3htYWlsLmNvbTCC\n" +
            "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCEvBFYJmhW+8iixdK0zlzwprsuytUGW5BH\n" +
            "ye9EEkJzGzYfVnEO/v4wC3vEvlWqkwTxY/ydnneH+yo0msAN6IEt6IA+3eO55PAlooAF8b8I2e83\n" +
            "usRTK4YmooZc/2GYNk2WBXvVlMuWABMKJ/oQMXlM46gffd3Z+evbbptZ5vm+QEWjUlw8fsTALakq\n" +
            "JgKsrmGSNBVngx1qnm00DL/3yfR2DZHro4CDzRp4toQV3ofcnt6Nz43Z4YkAXZr5gqxge8BZ2n8P\n" +
            "raQo/5wSfWoPW79Z8lPvZSZv5UIGCUAXdt0qYb3awSDsPSnMrRl03V4XmOK3RDdYDPrWMvii+YrC\n" +
            "/vUCAwEAAaOCAtkwggLVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUF\n" +
            "BwMBMB0GA1UdDgQWBBR8ztcEh/lE/9fxcga6p7/b+x+pUTAfBgNVHSMEGDAWgBTrQjTQmLCrn/Qb\n" +
            "awj3zGQu7w4sRTAfBgNVHREEGDAWggthcGkuYm1vYi5jboIHYm1vYi5jbjCCAVYGA1UdIASCAU0w\n" +
            "ggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcCARYiaHR0cDovL3d3\n" +
            "dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUHAgIwgeowJxYgU3RhcnRDb20gQ2Vy\n" +
            "dGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqBvlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBh\n" +
            "Y2NvcmRpbmcgdG8gdGhlIENsYXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0\n" +
            "YXJ0Q29tIENBIHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug\n" +
            "aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4wNQYDVR0fBC4w\n" +
            "LDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMIGOBggrBgEFBQcB\n" +
            "AQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9z\n" +
            "ZXJ2ZXIvY2EwQgYIKwYBBQUHMAKGNmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5j\n" +
            "bGFzczEuc2VydmVyLmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8w\n" +
            "DQYJKoZIhvcNAQELBQADggEBAF/t9Bc14BV0OwXcFf4Bs8y+p1AdbMqualCvLzjS95Z9HbPGcbRl\n" +
            "W76XwaM7iFE1R4mR1lGBQsacbBHOCNeZURYWGAG5c/yqhqCmWCzVJxM88AhCzkEv98uKa3IqE1zY\n" +
            "lOpYn4cMVqpPgg47QXqUfQlRoh21UTTORgiHEUY+JYNIlIXLoHtHVR0886+pIAq5fFrCwMHF45Df\n" +
            "r8tuTASazhYJUlOiGQTVv5p8Kg1wJ0ftMs9xJpThcnpEWrngmnNH/8H05rvJ9dEHkpnAU4mL46Bb\n" +
            "rmQe3oNoGE5EISL9KGVUMeS9wcR2kx+VmGhnAh7kjn5KuEidgfajS3XlcJ5o9t0=\n" +
            "-----END CERTIFICATE-----";
private static final String CERT="-----BEGIN CERTIFICATE-----\n" +
            "MIIGLjCCBRagAwIBAgIDFCb6MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJJTDEWMBQGA1UE\n" +
            "ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln\n" +
            "bmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2\n" +
            "ZXIgQ0EwHhcNMTQxMTA3MDExNzM0WhcNMTUxMTA4MDIxMDQ2WjBJMQswCQYDVQQGEwJDTjEUMBIG\n" +
            "A1UEAxMLYXBpLmJtb2IuY24xJDAiBgkqhkiG9w0BCQEWFWhlc2hhb3l1ZUBmb3htYWlsLmNvbTCC\n" +
            "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCEvBFYJmhW+8iixdK0zlzwprsuytUGW5BH\n" +
            "ye9EEkJzGzYfVnEO/v4wC3vEvlWqkwTxY/ydnneH+yo0msAN6IEt6IA+3eO55PAlooAF8b8I2e83\n" +
            "usRTK4YmooZc/2GYNk2WBXvVlMuWABMKJ/oQMXlM46gffd3Z+evbbptZ5vm+QEWjUlw8fsTALakq\n" +
            "JgKsrmGSNBVngx1qnm00DL/3yfR2DZHro4CDzRp4toQV3ofcnt6Nz43Z4YkAXZr5gqxge8BZ2n8P\n" +
            "raQo/5wSfWoPW79Z8lPvZSZv5UIGCUAXdt0qYb3awSDsPSnMrRl03V4XmOK3RDdYDPrWMvii+YrC\n" +
            "/vUCAwEAAaOCAtkwggLVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUF\n" +
            "BwMBMB0GA1UdDgQWBBR8ztcEh/lE/9fxcga6p7/b+x+pUTAfBgNVHSMEGDAWgBTrQjTQmLCrn/Qb\n" +
            "awj3zGQu7w4sRTAfBgNVHREEGDAWggthcGkuYm1vYi5jboIHYm1vYi5jbjCCAVYGA1UdIASCAU0w\n" +
            "ggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcCARYiaHR0cDovL3d3\n" +
            "dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUHAgIwgeowJxYgU3RhcnRDb20gQ2Vy\n" +
            "dGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqBvlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBh\n" +
            "Y2NvcmRpbmcgdG8gdGhlIENsYXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0\n" +
            "YXJ0Q29tIENBIHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug\n" +
            "aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4wNQYDVR0fBC4w\n" +
            "LDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMIGOBggrBgEFBQcB\n" +
            "AQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9z\n" +
            "ZXJ2ZXIvY2EwQgYIKwYBBQUHMAKGNmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5j\n" +
            "bGFzczEuc2VydmVyLmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8w\n" +
            "DQYJKoZIhvcNAQELBQADggEBAF/t9Bc14BV0OwXcFf4Bs8y+p1AdbMqualCvLzjS95Z9HbPGcbRl\n" +
            "W76XwaM7iFE1R4mR1lGBQsacbBHOCNeZURYWGAG5c/yqhqCmWCzVJxM88AhCzkEv98uKa3IqE1zY\n" +
            "lOpYn4cMVqpPgg47QXqUfQlRoh21UTTORgiHEUY+JYNIlIXLoHtHVR0886+pIAq5fFrCwMHF45Df\n" +
            "r8tuTASazhYJUlOiGQTVv5p8Kg1wJ0ftMs9xJpThcnpEWrngmnNH/8H05rvJ9dEHkpnAU4mL46Bb\n" +
            "rmQe3oNoGE5EISL9KGVUMeS9wcR2kx+VmGhnAh7kjn5KuEidgfajS3XlcJ5o9t0=\n" +
            "-----END CERTIFICATE-----";

然后使用OkIO里的Buffer类进行读取并设置

SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(new Buffer().writeUtf8(CERT).inputStream());
client.setSslSocketFactory(sslSocketFactory);

当然之前我们已将将其放到assets目录下了,就不用这么麻烦的导出字符串,赋值等操作,我们之间使用该文件即可

try {
    SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(getAssets().open(CERT_FILENAME));
    client.setSslSocketFactory(sslSocketFactory);

} catch (IOException e) {
    e.printStackTrace();
}

这时候你请求一下,就会发现可以成功请求了,并且服务器返回了信息

android response获取内容 android restful api_https_04

请求的问题解决了,那么还遗留了一个重要的问题,就是如果使用Restful API,我们的APPLICATION_ID和API_KEY就直接暴露在客户端了。有没有一种方法可以提高一定的安全性呢,方法是有的,只不过只是相对来说安全一点,但是如果人家想搞你,那也是没有办法的,方法就是使用jni获得这两个值,由jni层返回这两个字符串。

声明java层方法

public class KeyUtil {
    static {
        System.loadLibrary("key");
    }
    public static native String getApplicationId();
    public static native String getAPIKey();
}

使用alt+enter生成jni方法,并在里面返回这两个值

#include <jni.h>

JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getApplicationId(JNIEnv *env, jclass instance) {
    char returnValue[]="8dcb9fe*************ab19e7dfd9d";
    return (*env)->NewStringUTF(env, returnValue);
}

JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getAPIKey(JNIEnv *env, jclass instance) {
    char returnValue[]="aebe3b71c9b*****************e560b1";
    return (*env)->NewStringUTF(env, returnValue);
}

在java层要获得这两个值只要使用对应的静态方法即可。

KeyUtil.getApplicationId();
KeyUtil.getAPIKey();

最终,我们的代码也就成了这样子

public class MainActivity extends AppCompatActivity {
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private static final String URL ="https://api.bmob.cn/1/classes/Person";
    private static final String CERT_FILENAME ="bmob.cer";
    private static final Gson gson=new Gson();
    private static final OkHttpClient client=new OkHttpClient();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(getAssets().open(CERT_FILENAME));
                    client.setSslSocketFactory(sslSocketFactory);

                } catch (IOException e) {
                    e.printStackTrace();
                }


                Person person=new Person("张三","杭州",20);
                RequestBody body = RequestBody.create(JSON, gson.toJson(person));
                Request insert=new Request.Builder()
                        .url(URL)
                        .addHeader("Content-Type","application/json")
                        .addHeader("X-Bmob-Application-Id", KeyUtil.getApplicationId())
                        .addHeader("X-Bmob-REST-API-Key",KeyUtil.getAPIKey())
                        .post(body)
                        .build();

                client.newCall(insert).enqueue(new Callback() {
                    @Override
                    public void onFailure(Request request, IOException e) {

                    }

                    @Override
                    public void onResponse(Response response) throws IOException {
                        Log.e("TAG",response.body().string());
                    }
                });

            }
        });
    }

}

这种方法只能提高一定的安全性但是不能完全避免,人家只要反汇编你的so就能看到这两个值,你要再加强安全性就只能在jni层进行加密解密等操作了,总之就是不要直接返回字符串。

之后你在Bmob后台就能看到数据

android response获取内容 android restful api_jni_05

Bmob为懒人提供了很好的后端解决方案,我们几乎不用写一句代码,就可以搭建一个强大的后台服务。