浅笑博客
世界上最大的谎话,莫过于在最难的时候说一了句:“我没事。”
浅笑博客
Android使用QQsdk的个人登录方案设计与实现
Android使用QQsdk的个人登录方案设计与实现

分享一套个人在实际开发过程中设计和实现和一套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&timestamp=%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."&timestamp=".$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的有效期。

关于本文,如有任何建议或意见,欢迎评论交流,谢谢!

没有标签
首页      Android开发      Android使用QQsdk的个人登录方案设计与实现

发表评论

textsms
account_circle
email

浅笑博客

Android使用QQsdk的个人登录方案设计与实现
分享一套个人在实际开发过程中设计和实现和一套qq登录的方案。 qqsdk相信大多数人都用过(QQ互联平台:https://connect.qq.com/),在此之前,我在做我的第一款App的时候第一次接触过…
扫描二维码继续阅读
2020-08-21