|  |  |  |  | --- | 
					
						
							|  |  |  |  | layout: post | 
					
						
							|  |  |  |  | title: 自制微信二维码登录API | 
					
						
							|  |  |  |  | tags: [微信, 登录, 验证, PHP] | 
					
						
							|  |  |  |  | --- | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   二维码登录看来也不是什么复杂的东西嘛<!--more-->     | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  | # 起因
 | 
					
						
							|  |  |  |  |   前段时间我用了一位大佬的认证公众号做了一个[微信推送](/2021/03/23/wxpush.html)的API,并且希望把它做成像WxPusher那样的平台。但是吧……我想了想,现在微服务不是比较火嘛,WxPusher那种的实在是太臃肿了,而且还是用Java写的,那就更加垃圾了,所以我决定把功能模块化,让每一个功能都可以单独运行,互不影响。    | 
					
						
							|  |  |  |  |   而今天我要做的就是允许A用户(开发者)使用微信扫描二维码的方式去获取B用户(客户)的用户ID。当然这种功能的话肯定还是用PHP完成的啦,所以代码如下: | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  | # 代码
 | 
					
						
							|  |  |  |  | ```php | 
					
						
							|  |  |  |  | <?php | 
					
						
							|  |  |  |  | $appid='公众号APPID'; | 
					
						
							|  |  |  |  | $secret='公众号Secret'; | 
					
						
							|  |  |  |  | $token='和配置的Token配置一致即可'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ini_set('session.gc_maxlifetime', 7200); | 
					
						
							|  |  |  |  | session_id('Storagepush'); | 
					
						
							|  |  |  |  | session_start(); | 
					
						
							|  |  |  |  | if(!json_decode(file_get_contents('https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token='.$_SESSION['access_token']),true)['ip_list']){ | 
					
						
							|  |  |  |  | $_SESSION['access_token']=json_decode(file_get_contents('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$secret),true)['access_token']; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | if(isset($_GET["action"])&&isset($_GET["key"])){ | 
					
						
							|  |  |  |  | $_GET["key"]=addslashes($_GET["key"]); | 
					
						
							|  |  |  |  | if(strlen($_GET["key"])<6||strlen($_GET["key"])>32){ | 
					
						
							|  |  |  |  |     die("Bad Key"); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | if($_GET["action"] == "set"){ | 
					
						
							|  |  |  |  |     echo file_get_contents('https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token='.$_SESSION['access_token'], false, stream_context_create(array('http' => array('method'=>'POST','header'=>"Content-Type: application/json;charset=utf-8",'content'=>'{"expire_seconds": 3600, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "auth'.$_GET["key"].'"}}}')))); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | if ($_GET["action"] == "get") { | 
					
						
							|  |  |  |  |     if(isset($_SESSION['wxboxauth'.$_GET["key"]])){ | 
					
						
							|  |  |  |  |         echo $_SESSION['wxboxauth'.$_GET["key"]]; | 
					
						
							|  |  |  |  |     }else{ | 
					
						
							|  |  |  |  |         echo "Empty"; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | }else{ | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | $timestamp=$_GET["timestamp"]; | 
					
						
							|  |  |  |  | $nonce=$_GET["nonce"]; | 
					
						
							|  |  |  |  | $tmpArr=array($token, $timestamp, $nonce); | 
					
						
							|  |  |  |  | sort($tmpArr, SORT_STRING); | 
					
						
							|  |  |  |  | if( sha1(implode($tmpArr)) == $_GET["signature"] ){ | 
					
						
							|  |  |  |  | if($_GET["echostr"]){ | 
					
						
							|  |  |  |  | echo $_GET["echostr"]; | 
					
						
							|  |  |  |  | }else{ | 
					
						
							|  |  |  |  | //  加载XML内容 | 
					
						
							|  |  |  |  | $content = file_get_contents("php://input"); | 
					
						
							|  |  |  |  | $p = xml_parser_create(); | 
					
						
							|  |  |  |  | xml_parse_into_struct($p, $content, $vals, $index); | 
					
						
							|  |  |  |  | xml_parser_free($p); | 
					
						
							|  |  |  |  | if(($vals[$index['EVENT'][0]]['value'] == "subscribe" || $vals[$index['EVENT'][0]]['value'] == "SCAN") && isset($vals[$index['EVENTKEY'][0]]['value'])){ | 
					
						
							|  |  |  |  |     if($vals[$index['EVENT'][0]]['value'] == "subscribe"){ | 
					
						
							|  |  |  |  |         $vals[$index['EVENTKEY'][0]]['value'] = substr($vals[$index['EVENTKEY'][0]]['value'],8); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     $_SESSION['wxbox'.$vals[$index['EVENTKEY'][0]]['value']] = $vals[$index['FROMUSERNAME'][0]]['value']; | 
					
						
							|  |  |  |  |     echo '<xml> | 
					
						
							|  |  |  |  |   <ToUserName><![CDATA['.$vals[$index['FROMUSERNAME'][0]]['value'].']]></ToUserName> | 
					
						
							|  |  |  |  |   <FromUserName><![CDATA['.$vals[$index['TOUSERNAME'][0]]['value'].']]></FromUserName> | 
					
						
							|  |  |  |  |   <CreateTime>'.time().'</CreateTime> | 
					
						
							|  |  |  |  |   <MsgType><![CDATA[text]]></MsgType> | 
					
						
							|  |  |  |  |   <Content><![CDATA[成功请求登录!]]></Content> | 
					
						
							|  |  |  |  | </xml>'; | 
					
						
							|  |  |  |  | }else{ | 
					
						
							|  |  |  |  | echo "success"; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | }else{ | 
					
						
							|  |  |  |  |     echo "Fail"; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # 使用文档
 | 
					
						
							|  |  |  |  | ## 接口调用方法
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | | 参数 | 是否必填 | 请求方法 | 内容 | | 
					
						
							|  |  |  |  | | - | - | - | - | | 
					
						
							|  |  |  |  | | action | 是 | GET | set/get | | 
					
						
							|  |  |  |  | | key | 是 | GET | 6-32字节长度的随机字符串 | | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ## 说明
 | 
					
						
							|  |  |  |  |   开发者需要先使用set方法设置一个存储用户OPENID的盒子,使用key来命名,为了避免重复,这里推荐使用32位的UUID作为名称,请求完成之后会获得一个有效时长为1小时的二维码的ticket和二维码的地址,可以如果希望自己生成二维码,可以使用返回的URL作为二维码的内容,或者也可以调用微信的Ticket转二维码接口,在`https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=`后面加上获得的Ticket就可以直接获得二维码的图片。    | 
					
						
							|  |  |  |  |   获取OPENID需要使用get方法去获得命名为key的盒子,如果用户已经扫描了二维码,那么调用此接口会直接返回扫描者的OPENID,如果没有扫描或者用户扫描后超过了2个小时,就会返回Empty,以表示盒子为空。 | 
					
						
							|  |  |  |  | ## 使用示例
 | 
					
						
							|  |  |  |  |   像我之前写的[微信推送](/2021/03/23/wxpush.html)中不是就需要这个用户的OPENID嘛,假如一个网站想要主动给某些用户推送消息,就可以先调用这个接口获得用户的OPENID,然后存起来,有必要时可以直接使用微信推送来给用户推送信息。另外这个OPENID是唯一的,所以假如想做网站二维码扫描绑定登录同样也可以使用这个接口。具体实现就非常简单了,所以示例代码我就不写了。    | 
					
						
							|  |  |  |  | ## 注意事项
 | 
					
						
							|  |  |  |  |   像这个代码依然不防滥用,并且我也没有检验过安全性,是有很大可能有漏洞的。所以有懂安全的大佬也可以指点一下,来完善这个项目。 |