Android使用Bmob移动后端云Restful API需要注意的问题
如果你自己想做一个客户端玩玩,但是又不想去搭建后台服务器,显然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
原来是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();}
但是这种方法有安全隐患,我们还是使用证书吧。
首先用Chrome打开https://api.bmob.cn/,然后点击链接左边的锁的图形,切到连接项,点击证书信息,如下图
然后将证书导出即可,之后一直下一步即可。
这里假设导出的证书名字为bmob.cer,将其放到assets目录下。
然后编写一个https验证的工具类。
/** * User:lizhangqu([email protected]) * 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
然后将其赋值给一个字符串
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();}
这时候你请求一下,就会发现可以成功请求了,并且服务器返回了信息
请求的问题解决了,那么还遗留了一个重要的问题,就是如果使用Restful API,我们的APPLICATION_ID和API_KEY就直接暴露在客户端了。有没有一种方法可以提高一定的安全性呢,方法是有的,只不过只是相对来说安全一点,但是如果人家想搞你,那也是没有办法的,方法就是使用jni获得这两个值,由jni层返回这两个字符串。
Android Studio下ndk的开发环境搭建见Android Studio使用新的Gradle构建工具配置NDK环境,这里不再累赘。
声明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 JNICALLJava_cn_edu_zafu_bmobdemo_util_KeyUtil_getApplicationId(JNIEnv *env, jclass instance) { char returnValue[]="8dcb9fe*************ab19e7dfd9d"; return (*env)->NewStringUTF(env, returnValue);}JNIEXPORT jstring JNICALLJava_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后台就能看到数据
Bmob为懒人提供了很好的后端解决方案,我们几乎不用写一句代码,就可以搭建一个强大的后台服务。
最后放上源码。
http://download.csdn.net/detail/sbsujjbcy/9135981
更多相关文章
- DIY osc android 客户端 之 方法论
- Android字符串进阶之三:字体属性及测量(FontMetrics)
- Android webview和js互相调用实现方法
- 深入解析android log的分析方法(1)
- Android增量升级的方法和原理
- android ListView常见问题解决方法(滚动背景变黑,去除滑动时阴影,拖