标签:android bmob restful https jni
如果你自己想做一个客户端玩玩,但是又不想去搭建后台服务器,显然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(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
然后将其赋值给一个字符串
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 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后台就能看到数据
Bmob为懒人提供了很好的后端解决方案,我们几乎不用写一句代码,就可以搭建一个强大的后台服务。
版权声明:本文为博主原创文章,未经博主允许不得转载。
Android使用Bmob移动后端云Restful API需要注意的问题
标签:android bmob restful https jni
原文地址:http://blog.csdn.net/sbsujjbcy/article/details/48709073