一、登录
1、登录的基本流程:(以账密登录为例)
1)调用X_SDK的登录方法,进行SDK登录操作。即弹出登录框,等待用户输入账号密码。
2) X_SDK与SDK服务器进行通信,向SDK服务器传递对应信息。即把账号密码等信息传给服务器。
3)SDK服务器向X_SDK返回token,token中包含了玩家的验证信息。1、2、3 这三步可以理解为调用X_SDK的登录方法,然后等待服务器回调token。
4)X_SDK把token等信息传递给游戏。到这一步X_SDK的任务就完成了。
5)游戏把拿到的token信息,传递给游戏服务器。游戏服务器将数据传递给SDK服务器进行验证。
6)SDK服务器将验证结果返回给游戏服务器。
7)游戏服务器根据返回的结果。返回对应的信息和生成唯一的PlayerID给客户端。
2、账密登录具体操作
1)X_SDK与SDK服务器间的通信。
使用HttpPost向服务器提交数据。
示例代码:(这只是个简单的示例,完善的代码请自行修改)
//采用post方法向服务器提交数据
public static void postServer(Context mContext, final String url, final List<NameValuePair> params, final postServerListener listener){
class NetworkThread implements Runnable {
@Override
public void run() {
//创建HttpPost对象
HttpPost httpPost = new HttpPost(url);
HttpResponse httpResponse = null;
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); // 设置httpPost请求参数,格式为UTF_8
try {
httpResponse = new DefaultHttpClient().execute(httpPost);
}catch (SSLException e){
SSLSocketFactory.getSocketFactory().setHostnameVerifier(new AllowAllHostnameVerifier());
httpResponse = new DefaultHttpClient().execute(httpPost);
}
int resultCode = httpResponse.getStatusLine().getStatusCode();
if (resultCode == 200) { //连接返回成功
String result = EntityUtils.toString(httpResponse.getEntity());// 使用getEntity方法活得返回结果
JSONObject json = new JSONObject(result);
int code = json.getInt("code");
String errMsg = json.optString("message", null);
if(code==300){ //服务器返回成功
listener.Success(json);
}else{
listener.Fail(code+"",errMsg);
}
}else{
listener.Fail(resultCode+"","连接失败");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
listener.Fail("","ClientProtocolException");
} catch (IOException e) {
e.printStackTrace();
listener.Fail("","IOException");
} catch (JSONException e) {
e.printStackTrace();
listener.Fail("","JSONException");
}
}
}
new Thread(new NetworkThread()).start();
}
public interface postServerListener{
public void Success(JSONObject json);
public void Fail(String code,String msg);
}
X_SDK与SDK服务器之间的通讯需要保证请求的参数不被恶意修改。保证请求数据的一致性。
先介绍一下AES(对称加密算法)和RSA(非对称加密算法)。
对称加密算法:
客服端和服务器端共同确定一个用来加密和解密的秘钥。然后客服端在请求服务器是通过该秘钥对数据进行加密,服务器端在接收到请求之后使用该秘钥对数据进行解密。
优势在于加密效率高,而缺点是秘钥需要共享给客户端,具有泄露的风险。
非对称加密算法:
服务器端生成公钥和私钥,把私钥发送给客户端。客服端在请求服务器时,通过公钥对数据进行加密。服务器端接收到请求之后,使用私钥对加密的数据进行解密。
优势在于不需要共享私钥,避免了私钥泄露的风险,而缺点是加密效率低,数据量大时较为耗时。
再介绍一下MessageDigest数字摘要。
它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。消息摘要后的结果是固定长度,无论你的数据有多大,哪怕是只有一个字节或者是一个G的文件,摘要后的结果都是固定长度。数字摘要常用算法是MD5、SHA、CRC 等,都是哈希Hash算法。
示例代码:
//使用对称加密算法和MD5数字摘要的sign生成方法
public static String md5Decode32(Map<String, String> data, String time) {
String key="XXXXXXXXX"; //对称加密算法中的秘钥
String content = "";//需要保证不被修改的参数
for (String keyname:data.keySet()) {
content=content+keyname+ data.get(keyname);
}
content=content+time+ key;//注意:time一定要传进来,不可以在方法中直接生成。不然时间是对不上的
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));//使用MD5算法进行数字摘要
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("NoSuchAlgorithmException",e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UnsupportedEncodingException", e);
}
StringBuilder hex = new StringBuilder(hash.length * 2);//对生成的16字节数组进行补零操作
for (byte b : hash) {
if ((b & 0xFF) < 0x10){
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();//返回sign
}
还有密码是不要明文传输和明文保存的,所以要对密码进行加密操作。
加密操作可以使用Android中的Cipher(详细或简单使用)。Cipher可以提供加密和解密的功能。生成Cipher对象时可以传入RSA/ECB/PKCS1Padding或者DES/OFB32/PKCS5Padding两种模式。但不管使用哪种模式,都要和后台保持一致。
示例代码:
//把明文加密
public static String encrypt(String content, String key) {
try {
PublicKey pubkey = "XXXXXXX";
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");//生成Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, pubkey);//操作模式为加密(Cipher.ENCRYPT_MODE),pubkey为公钥
byte plaintext[] = content.getBytes("UTF-8");
byte[] output = cipher.doFinal(plaintext);//得到加密后的字节数组
String s = new String(android.util.Base64.encode(output, android.util.Base64.DEFAULT));
return s;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
登录的示例代码:
private void SignIn(Context mContext,String userName, String psw){
String url = XXXXXX; //账密登录API
String encryptPsw = Rsa.encrypt(psw, LOGIN_PUBLIC_KEY); //加密密码
//设置参数
List<ValuePair> params = new ArrayList<ValuePair>();
params.add(new BasicValuePair("user_name", userName));
params.add(new BasicValuePair("encryptPsw", encryptPsw));
//设置url
String time = System.currentTimeMillis()+"";
Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>(); //有序集合,默认是升序
MD5_Parameter_Map.put("user_name",userName);
MD5_Parameter_Map.put("encryptPsw",encryptPsw);
String sign = Rsa.md5Decode32(MD5_Parameter_Map,time) ;
params.add(new BasicNameValuePair("sign", sign));
params.add(new BasicNameValuePair("time", time));
//请求
postServer(mContext,url,params, new postServerListener() {
@Override
public void Success(JSONObject json) {
//成功
}
@Override
public void Fail(String code, String msg) {
//失败
}
});
}
登录成功后一般返回token(已登陆用户的识别码)、UserName(用户名)、uid(用户id)等信息。需要把这些信息(并不一定是全部)返回到游戏,让游戏完成登录。
同时也把这些信息保存起来,可以保存到SharedPreferences。
3、自动登录的具体操作
用户登录时不需要每次都弹出账密登录框,在一定时间内自动登录,方便用户登录游戏。
自动登录一般可以使用token登录。在账密登录成功后会返回token给客户端,同时也会把该token和该用户的对应关系缓存在服务器端。客户端在后续的请求接口中就不用每次都传入用户名和密码,只需要传入token即可。服务器会根据token确定客户端的身份。同时token可设置生效时间,token失效之后,客户端重新请求token。
自动登录示例代码:
private void SignInAuto(Context mContext,UserInfo user){
String url = XXXXXX; //自动登录API
//设置参数
List<ValuePair> params = new ArrayList<ValuePair>();
params.add(new BasicValuePair("autoStr", user.autoStr));
//设置url
String time = System.currentTimeMillis()+"";
Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>();
MD5_Parameter_Map.put("user_name",userName);
String sign = Rsa.md5Decode32(MD5_Parameter_Map,time) ;
params.add(new BasicNameValuePair("sign", sign));
params.add(new BasicNameValuePair("time", time));
//请求
postServer(mContext,url,params, new postServerListener() {
@Override
public void Success(JSONObject json) {
//成功
}
@Override
public void Fail(String code, String msg) {
//失败
}
});
}
4、手机号登录的具体操作
手机号登录需要先向服务端请求验证码。
获取到验证码之后,再使用手机号和验证码向服务器发起注册请求。
手机号登录示例代码:
//登录框点击“获取验证码”时调用
private void getSMSCode(Context mContext,String phone,String type){
String url = XXXXXX; //验证码获取API
//设置参数
List<ValuePair> params = new ArrayList<ValuePair>();
params.add(new BasicValuePair("phone", phone));
params.add(new BasicValuePair("type",type));
//设置url
String time = System.currentTimeMillis()+"";
Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>();
MD5_Parameter_Map.put("phone",phone);
MD5_Parameter_Map.put("type",type);
String sign = Rsa.md5Decode32(MD5_Parameter_Map,time) ;
params.add(new BasicNameValuePair("sign", sign));
params.add(new BasicNameValuePair("time", time));
//请求
postServer(mContext,url,params, new postServerListener() {
@Override
public void Success(JSONObject json) {
int code = json.getInt("code");
String errMsg = json.optString("message", null);
switch (code) {
case 0:
//获取验证码成功
break;
default:
//获取验证码失败
break;
}
}
@Override
public void Fail(String code, String msg) {
//失败
}
});
}
//登录框点击“确认”时调用
private void SignInPhone(Context mContext,String phone,String code){
String url = XXXXXX; //手机号登录API
//设置参数
List<ValuePair> params = new ArrayList<ValuePair>();
params.add(new BasicValuePair("phone", phone));
params.add(new BasicValuePair("code",code));
//设置url
String time = System.currentTimeMillis()+"";
Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>();
MD5_Parameter_Map.put("phone",phone);
MD5_Parameter_Map.put("code",code);
String sign = Rsa.md5Decode32(MD5_Parameter_Map,time) ;
params.add(new BasicNameValuePair("sign", sign));
params.add(new BasicNameValuePair("time", time));
//请求
postServer(mContext,url,params, new postServerListener() {
@Override
public void Success(JSONObject json) {
//成功
}
@Override
public void Fail(String code, String msg) {
//失败
}
});
}
二、注册
账号注册和手机号注册 都和登录的步骤相似,只需要更换对应的API即可。
一键注册可以使用手机设备ID作为唯一标识,进行注册。
上一篇: 手游SDK-概述
下一篇: 手游SDK-登录界面