分享一套个人在实际开发过程中设计和实现和一套qq登录的方案。
qqsdk相信大多数人都用过(QQ互联平台:https://connect.qq.com/),在此之前,我在做我的第一款App的时候第一次接触过,当时对Android开发也不是很熟悉,那时就是通过参考官方给的那个demo源码仿照它的实现。后来也在部分开发的应用中用到过,最近一次开发中,为了更好更快的实现登录,我特别地设计了一套个人登录方案,方便自己地使用。
简单来说,就是2步。第一步,调用QQ登录,用户授权登录成功后获取access_token。然后第二步,调用PHP实现的接口通过 access_token 在服务端进行用户信息解析并进行系统的注册和登录,并返回登录结果(自行生成一个应用token)。当然在此过程中为了安全,可以同时加一下客户端设备IMEI等唯一识别码信息来进行认证。
有什么好处呢?传统登录授权后(获取access_token后)需要再次在客户端请求一次获取用户基本信息,然后再通过接口与服务端进行注册和登录。这样获取access_token后,因为qqsdk可以直接使用php版通过 access_token 和openid获取个人信息,而openid又可以通过 access_token 获取到,同时也能获取到unionid,所以直接通过服务端写一个接口来处理这些事,那就省去了一步请求过程,从而使得更加方便。同时,比如说我的软件需要用户自己的昵称(默认是QQ昵称,之后支持更改),这样也可以一步直接获取数据库中用户的个人信息进行返回等。
直接上代码:
//初始化Tencent对象
Tencent tencent = Tencent.createInstance(APP_ID,context,AUTHORITIES);
//调用QQ授权登录
tencent.login((Activity) context, "all", new IUiListener() {
@Override
public void onComplete(Object o) {
//授权成功
JSONObject jsonObject = (JSONObject) o;
try {
String token = jsonObject.getString(Constants.PARAM_ACCESS_TOKEN);
String expires = jsonObject.getString(Constants.PARAM_EXPIRES_IN);
String openId = jsonObject.getString(Constants.PARAM_OPEN_ID);
//保存Session会话信息
MainModel.this.presenter.mTencent.saveSession(jsonObject);
if (!TextUtils.isEmpty(token) && !TextUtils.isEmpty(expires)
&& !TextUtils.isEmpty(openId)) {
//登录成功设置openid和token
MainModel.this.presenter.mTencent.setAccessToken(token, expires);
MainModel.this.presenter.mTencent.setOpenId(openId);
}
//准备获取IMEI连同access_token传到服务端进行认证并获取用户信息进行登录和注册
final String imei = ImeiUtils.getImei(context);
if(TextUtils.isEmpty(imei)){
MainModel.this.presenter.mView.Toast("获取手机IMEI错误,请检查是否授予IMEI权限");
return;
}
MainModel.this.presenter.mView.openLoadingDialog("正在登录");
final String access_token = MainModel.this.presenter.mTencent.getAccessToken();
//加时间戳是为了时间有效性验证
final String timestamp = String.valueOf(System.currentTimeMillis()/1000);
//加签名,防数据篡改
final String parsign = EncryptUtils.encryptSHA1ToString(String.format(
"imei=%s&access_token=%s×tamp=%spjbj",
imei,access_token,timestamp));
new Thread(new Runnable() {
@Override
public void run() {
Map<String, String> parms = new HashMap<>();
parms.put("imei", imei);
parms.put("access_token", access_token);
parms.put("timestamp", timestamp);
parms.put("parsign", parsign);
String res = HttpConnectionUtil.getHttp().postRequset("http://qianxiao.fun/app/pojiebiji/login.php",parms);
LogUtils.i(res);
try {
final JSONObject registerobj = new JSONObject(res);
final int code = registerobj.getInt("code");
final JSONObject dataobj = registerobj.getJSONObject("data");
final String msg = dataobj.getString("msg");
final String unionid = dataobj.optString("unionid","");
final String nick = dataobj.optString("nickname","");
//……
final String head_url = dataobj.optString("head_url","");
final String token = dataobj.optString("token","");
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if(code >= 0){
MySpUtils.save("token",token);
MainModel.this.presenter.mView.Toast(nick+"登录成功");
if(myUserInfo == null){
myUserInfo = new MyUserInfo("");
}
myUserInfo.setUid(unionid);
myUserInfo.setNick(nick);
myUserInfo.setHead_url(head_url);
MainModel.this.presenter.mView.loginSuccess(nick,head_url);
//……
MySpUtils.SaveObjectData("myUserInfo",myUserInfo);
}else{
if(code == -2){
MainModel.this.presenter.mView.Toast("请检查手机时间是否正确后重试");
}else{
MainModel.this.presenter.mView.Toast(msg);
}
}
}
});
} catch (final JSONException e) {
LogUtils.e(e.toString());
MainModel.this.presenter.mView.Toast(e.toString());
}
MainModel.this.presenter.mView.closeLoadingDialog();
}
}).start();
} catch (JSONException e) {
//JSONException
}
}
@Override
public void onError(UiError uiError) {
MainModel.this.presenter.mView.Toast("授权失败");
}
@Override
public void onCancel() {
MainModel.this.presenter.mView.Toast("授权取消");
}
});
下面是服务端php:
qqsdk下载:https://wiki.connect.qq.com/sdk%E4%B8%8B%E8%BD%BD
<?php
//此声明非常重要 指明返回json数据
header('Content-Type:application/json');
//引入这个qqsdk php版的这个php,方便待会获取用户信息
require_once("./API/qqConnectAPI.php");
//连接数据库
include("./connectdb.php");
mysql_select_db("mydb");
function ispostnull($a){
if(empty($_POST[$a]) || !isset($_POST[$a]) || $_POST[$a]=='' || $_POST[$a]==null){
return true;
}
return false;
}
if(ispostnull('imei')||ispostnull('access_token')||ispostnull('timestamp')||ispostnull('parsign')){
$ret["msg"] = "参数为空";
retn(-1,$ret);
}else{
$imei = $_POST['imei'];
$access_token = $_POST['access_token'];
$nowtime = time();
$requesttime = $_POST['timestamp'];
$parsign = $_POST['parsign'];
//时间有效性验证
if ($nowtime-$requesttime>60 || $requesttime-$nowtime>60) {
$ret["msg"] = "ERROR";
retn(-2,$ret);
}else{
//签名验证
if(strnatcasecmp(sha1("imei=".$imei."&access_token=".$access_token."×tamp=".$requesttime."pjbj"), $parsign)){
$ret["msg"] = "ERROR";
retn(-3,$ret);
}else{
//验证都通过后解析access_token获取用户unionid(QQ用户唯一识别码)、client_id(客户端id)以及openid(对应用的唯一id)
$tokenret = get_curl("https://openmobile.qq.com/oauth2.0/me?access_token=".$access_token."&unionid=1");
$tokenret = substr($tokenret,10,strlen($tokenret)-13);
$tokenret = json_decode($tokenret,true);
if($tokenret['error']){
$ret["msg"] = $tokenret['error_description'];
retn(-4,$ret);
}else{
//对client_id简单进行验证一下
$client_id = $tokenret['client_id'];
if($client_id != "101570841"){
$ret["msg"] = "APPID验证失败";
retn(-5,$ret);
}else{
$openid = $tokenret['openid'];
$unionid = $tokenret['unionid'];
$ret["unionid"] = $unionid;
//这里调用QQsdk php中的QC类用access_token和openid初始化一个qc对象
$qc = new QC($access_token,$openid);
//然后使用这个qc对象即可获取用户信息
$arr = $qc->get_user_info();
//返回的信息包含了昵称nickname、头像figureurl_qq_2等基本信息
$ret["head_url"] = $arr['figureurl_qq_2'];
//然后就是你个人个数据库操作了
$sql=mysql_query("SELECT * FROM user WHERE unionid ='$unionid'");
$result=mysql_fetch_assoc($sql);
if(!empty($result)){
//存在该用户
$ret["msg"] = "登录成功";
$ret["nickname"] = $result['nickname'];
//这里可自行设计算法返回一个应用的token,之后的请求都携带access_token和这个token进行登录有效性判断和用户身份鉴别
$token = sha1(md5("dhbtt2q0w4pldler1q8wuhnhn8v8fmaj".strtolower(strrev(substr($unionid,4))))."nc8fldf1v2iaea11h9vatdcozvnh7uhz".strtolower($openid).$client_id.$imei);
$ret["token"] = $token;
retn(0,$ret);
}else{
$nickname = $arr["nickname"];
$ret["nickname"] = $nickname;
$sql2 = "INSERT INTO user (unionid,nickname) VALUES ('$unionid','$nickname')";
if (mysql_query($sql2)) {
$ret["msg"] = "注册成功";
//同上
$token = sha1(md5("dhbtt2q0w4pldler1q8wuhnhn8v8fmaj".strtolower(strrev(substr($unionid,4))))."nc8fldf1v2iaea11h9vatdcozvnh7uhz".strtolower($openid).$client_id.$imei);
$ret["token"] = $token;
retn(1,$ret);
} else {
$ret["msg"] = "数据库添加数据失败";
retn(-5,$ret);
}
}
}
}
}
}
}
function get_curl($url, $paras = array()) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
//关闭https验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept:*/*";
$httpheader[] = "Accept-Encoding:gzip,deflate,sdch";
$httpheader[] = "Accept-Language:zh-CN,zh;q=0.8";
$httpheader[] = "Connection:close";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if ($paras['ctime']) { // 连接超时
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $paras['ctime']);
}
if ($paras['rtime']) { // 读取超时
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $paras['rtime']);
}
if ($paras['post']) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $paras['post']);
}
if ($paras['header']) {
curl_setopt($ch, CURLOPT_HEADER, true);
}
if ($paras['cookie']) {
curl_setopt($ch, CURLOPT_COOKIE, $paras['cookie']);
}
if ($paras['refer']) {
curl_setopt($ch, CURLOPT_REFERER, $paras['refer']);
}
if ($paras['ua']) {
curl_setopt($ch, CURLOPT_USERAGENT, $paras['ua']);
} else {
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
}
if ($paras['nobody']) {
curl_setopt($ch, CURLOPT_NOBODY, 1);
}
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
$return_result = $ret;
curl_close($ch);
return $return_result;
}
function retn($cod,$str){
mysql_close();
exit(json_encode([
"code"=>$cod,
"data"=>$str
],JSON_UNESCAPED_UNICODE));
}
关键位置我已进行代码注释。
关于服务端返回的那个token只是为了之后的请求更安全而做的。之后的请求都携带access_token和token进行双重认证。关于有效期,其实判断的是access_token的有效期。
关于本文,如有任何建议或意见,欢迎评论交流,谢谢!
请登录后发表评论
注册
社交帐号登录