• 售前

  • 售后

热门帖子
入门百科

详解PHP实现付出宝小步伐用户授权的工具类

[复制链接]
蜜蜜ss2017 显示全部楼层 发表于 2021-10-26 13:18:49 |阅读模式 打印 上一主题 下一主题
背景
最近项目必要上线付出宝小程序,同时必要走用户的授权流程完成用户信息的存储,从前做过微信小程序的开发,本以为实现授权的过程是很简单的事变,但是再实现的过程中照旧遇到了不少的坑,因此记录一下实现的过程
学到的知识
      
  • 付出宝开放接口的调用模式以及实现方式  
  • 付出宝小程序授权的流程  
  • RSA加密方式
吐槽点
付出宝小程序的入口隐蔽的很深,没有微信小程序那么直接了当
付出宝小程序的开发者工具比力难用,编译时候比力卡,性能有很大的题目
每提交一次代码,付出宝小程序的体验码都要举行更换,比力繁琐,而且localStorage的东西不知道要如何删除
事先准备
      
  • 到付出宝开放平台注册一个开发者账号,并做好相应的认证等工作  
  • 创建一个小程序,并记录好相干的小程序信息,包括付出宝公钥,私钥,app公钥等,可以借鉴付出宝官方提供的相应的公钥生成工具来生成公钥和私钥,工具的下载地址:传送门  
  • 了解下付出宝小程序的署名机制,详细见https://docs.open.alipay.com/291/105974  
  • 熟悉下付出宝小程序获取用户信息的过程,详细见付出宝小程序用户授权指引
授权的步骤

授权时序图

实现流程
      
  • 客户端通过my.getAuthCode接口获取code,传给服务端  
  • 服务端通过code,调用获取token接口获取access_token,alipay.system.oauth.token(换取授权访问令牌)  
  • 通过token接口调用付出宝会员查询接口获取会员信息,alipay.user.info.share(付出宝会员授权信息查询接口)  
  • 将获取的用户信息生存到数据库
AmpHelper工具类
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: My
  5. * Date: 2018/8/16
  6. * Time: 17:45
  7. */
  8. namespace App\Http\Helper;
  9. use App\Http\Helper\Sys\BusinessHelper;
  10. use Illuminate\Support\Facades\Log;
  11. class AmpHelper
  12. {
  13.   const API_DOMAIN = "https://openapi.alipay.com/gateway.do?";
  14.   const API_METHOD_GENERATE_QR = 'alipay.open.app.qrcode.create';
  15.   const API_METHOD_AUTH_TOKEN = 'alipay.system.oauth.token';
  16.   const API_METHOD_GET_USER_INFO = 'alipay.user.info.share';
  17.   const SIGN_TYPE_RSA2 = 'RSA2';
  18.   const VERSION = '1.0';
  19.   const FILE_CHARSET_UTF8 = "UTF-8";
  20.   const FILE_CHARSET_GBK = "GBK";
  21.   const RESPONSE_OUTER_NODE_QR = 'alipay_open_app_qrcode_create_response';
  22.   const RESPONSE_OUTER_NODE_AUTH_TOKEN = 'alipay_system_oauth_token_response';
  23.   const RESPONSE_OUTER_NODE_USER_INFO = 'alipay_user_info_share_response';
  24.   const RESPONSE_OUTER_NODE_ERROR_RESPONSE = 'error_response';
  25.   const STATUS_CODE_SUCCESS = 10000;
  26.   const STATUS_CODE_EXCEPT = 20000;
  27.   /**
  28.    * 获取用户信息接口,根据token
  29.    * @param $code 授权码
  30.    * 通过授权码获取用户的信息
  31.    */
  32.   public static function getAmpUserInfoByAuthCode($code){
  33.     $aliUserInfo = [];
  34.     $tokenData = AmpHelper::getAmpToken($code);
  35.     //如果token不存在,这种主要是为了处理支付宝的异常记录
  36.     if(isset($tokenData['code'])){
  37.       return $tokenData;
  38.     }
  39.     $token = formatArrValue($tokenData,'access_token');
  40.     if($token){
  41.       $userBusiParam = self::getAmpUserBaseParam($token);
  42.       $url = self::buildRequestUrl($userBusiParam);
  43.       $resonse = self::getResponse($url,self::RESPONSE_OUTER_NODE_USER_INFO);
  44.       if($resonse['code'] == self::STATUS_CODE_SUCCESS){
  45.         //有效的字段列
  46.         $userInfoColumn = ['user_id','avatar','province','city','nick_name','is_student_certified','user_type','user_status','is_certified','gender'];
  47.         foreach ($userInfoColumn as $column){
  48.           $aliUserInfo[$column] = formatArrValue($resonse,$column,'');
  49.         }
  50.       }else{
  51.         $exceptColumns = ['code','msg','sub_code','sub_msg'];
  52.         foreach ($exceptColumns as $column){
  53.           $aliUserInfo[$column] = formatArrValue($resonse,$column,'');
  54.         }
  55.       }
  56.     }
  57.     return $aliUserInfo;
  58.   }
  59.   /**
  60.    * 获取小程序token接口
  61.    */
  62.   public static function getAmpToken($code){
  63.     $param = self::getAuthBaseParam($code);
  64.     $url = self::buildRequestUrl($param);
  65.     $response = self::getResponse($url,self::RESPONSE_OUTER_NODE_AUTH_TOKEN);
  66.     $tokenResult = [];
  67.     if(isset($response['code']) && $response['code'] != self::STATUS_CODE_SUCCESS){
  68.       $exceptColumns = ['code','msg','sub_code','sub_msg'];
  69.       foreach ($exceptColumns as $column){
  70.         $tokenResult[$column] = formatArrValue($response,$column,'');
  71.       }
  72.     }else{
  73.       $tokenResult = $response;
  74.     }
  75.     return $tokenResult;
  76.   }
  77.   /**
  78.    * 获取二维码链接接口
  79.    * 433ac5ea4c044378826afe1532bcVX78
  80.    * https://openapi.alipay.com/gateway.do?timestamp=2013-01-01 08:08:08&method=alipay.open.app.qrcode.create&app_id=2893&sign_type=RSA2&sign=ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE&version=1.0&biz_content=
  81.   {"url_param":"/index.html?name=ali&loc=hz", "query_param":"name=1&age=2", "describe":"二维码描述"}
  82.   */
  83.   public static function generateQrCode($mpPage = 'pages/index',$queryParam = [],$describe){
  84.     $param = self::getQrcodeBaseParam($mpPage,$queryParam,$describe );
  85.     $url = self::buildRequestUrl($param);
  86.     $response = self::getResponse($url,self::RESPONSE_OUTER_NODE_QR);
  87.     return $response;
  88.   }
  89.   /**
  90.    * 获取返回的数据,对返回的结果做进一步的封装和解析,因为支付宝的每个接口的返回都是由一个特定的  
  91.    * key组成的,因此这里直接封装了而一个通用的方法,对于不同的接口只需要更改相应的node节点就可以了
  92.    */
  93.   public static function getResponse($url,$responseNode){
  94.     $json = curlRequest($url);
  95.     $response = json_decode($json,true);
  96.     $responseContent = formatArrValue($response,$responseNode,[]);
  97.     $errResponse = formatArrValue($response,self::RESPONSE_OUTER_NODE_ERROR_RESPONSE,[]);
  98.     if($errResponse){
  99.       return $errResponse;
  100.     }
  101.     return $responseContent;
  102.   }
  103.   /**
  104.    * 获取请求的链接
  105.    */
  106.   public static function buildQrRequestUrl($mpPage = 'pages/index',$queryParam = []){
  107.     $paramStr = http_build_query(self::getQrBaseParam($mpPage,$queryParam));
  108.     return self::API_DOMAIN . $paramStr;
  109.   }
  110.   /**
  111.    * 构建请求链接
  112.    */
  113.   public static function buildRequestUrl($param){
  114.     $paramStr = http_build_query($param);
  115.     return self::API_DOMAIN . $paramStr;
  116.   }
  117.   /**
  118.    * 获取用户的基础信息接口
  119.    */
  120.   public static function getAmpUserBaseParam($token){
  121.     $busiParam = [
  122.       'auth_token' => $token,
  123.     ];
  124.     $param = self::buildApiBuisinessParam($busiParam,self::API_METHOD_GET_USER_INFO);
  125.     return $param;
  126.   }
  127.   /**
  128.    *获取二维码的基础参数
  129.    */
  130.   public static function getQrcodeBaseParam($page= 'pages/index/index',$queryParam = [],$describe = ''){
  131.     $busiParam = [
  132.       'biz_content' => self::getQrBizContent($page,$queryParam,$describe)
  133.     ];
  134.     $param = self::buildApiBuisinessParam($busiParam,self::API_METHOD_GENERATE_QR);
  135.     return $param;
  136.   }
  137.   /**
  138.    *获取授权的基础参数
  139.    */
  140.   public static function getAuthBaseParam($code,$refreshToken = ''){
  141.     $busiParam = [
  142.       'grant_type' => 'authorization_code',
  143.       'code' => $code,
  144.       'refresh_token' => $refreshToken,
  145.     ];
  146.     $param = self::buildApiBuisinessParam($busiParam,self::API_METHOD_AUTH_TOKEN);
  147.     return $param;
  148.   }
  149.   /**
  150.    * 构建业务参数
  151.    */
  152.   public static function buildApiBuisinessParam($businessParam,$apiMethod){
  153.     $pubParam = self::getApiPubParam($apiMethod);
  154.     $businessParam = array_merge($pubParam,$businessParam);
  155.     $signContent = self::getSignContent($businessParam);
  156.     error_log('sign_content ===========>'.$signContent);
  157.     $rsaHelper = new RsaHelper();
  158.     $sign = $rsaHelper->createSign($signContent);
  159.     error_log('sign ===========>'.$sign);
  160.     $businessParam['sign'] = $sign;
  161.     return $businessParam;
  162.   }
  163.   /**
  164.    * 公共参数
  165.    *
  166.    */
  167.   public static function getApiPubParam($apiMethod){
  168.     $ampBaseInfo = BusinessHelper::getAmpBaseInfo();
  169.     $param = [
  170.       'timestamp' => date('Y-m-d H:i:s') ,
  171.       'method' => $apiMethod,
  172.       'app_id' => formatArrValue($ampBaseInfo,'appid',config('param.amp.appid')),
  173.       'sign_type' =>self::SIGN_TYPE_RSA2,
  174.       'charset' =>self::FILE_CHARSET_UTF8,
  175.       'version' =>self::VERSION,
  176.     ];
  177.     return $param;
  178.   }
  179.   /**
  180.    * 获取签名的内容
  181.    */
  182.   public static function getSignContent($params) {
  183.     ksort($params);
  184.     $stringToBeSigned = "";
  185.     $i = 0;
  186.     foreach ($params as $k => $v) {
  187.       if (!empty($v) && "@" != substr($v, 0, 1)) {
  188.         if ($i == 0) {
  189.           $stringToBeSigned .= "$k" . "=" . "$v";
  190.         } else {
  191.           $stringToBeSigned .= "&" . "$k" . "=" . "$v";
  192.         }
  193.         $i++;
  194.       }
  195.     }
  196.     unset ($k, $v);
  197.     return $stringToBeSigned;
  198.   }
  199.   public static function convertArrToQueryParam($param){
  200.     $queryParam = [];
  201.     foreach ($param as $key => $val){
  202.       $obj = $key.'='.$val;
  203.       array_push($queryParam,$obj);
  204.     }
  205.     $queryStr = implode('&',$queryParam);
  206.     return $queryStr;
  207.   }
  208.   /**
  209.    * 转换字符集编码
  210.    * @param $data
  211.    * @param $targetCharset
  212.    * @return string
  213.    */
  214.   public static function characet($data, $targetCharset) {
  215.     if (!empty($data)) {
  216.       $fileType = self::FILE_CHARSET_UTF8;
  217.       if (strcasecmp($fileType, $targetCharset) != 0) {
  218.         $data = mb_convert_encoding($data, $targetCharset, $fileType);
  219.       }
  220.     }
  221.     return $data;
  222.   }
  223.   /**
  224.    * 获取业务参数内容
  225.    */
  226.   public static function getQrBizContent($page, $queryParam = [],$describe = ''){
  227.     if(is_array($queryParam)){
  228.       $queryParam = http_build_query($queryParam);
  229.     }
  230.     $obj = [
  231.       'url_param' => $page,
  232.       'query_param' => $queryParam,
  233.       'describe' => $describe
  234.     ];
  235.     $bizContent = json_encode($obj,JSON_UNESCAPED_UNICODE);
  236.     return $bizContent;
  237.   }
  238. }
复制代码
AmpHeler工具类关键代码剖析相干常量
  1. //支付宝的api接口地址
  2. const API_DOMAIN = "https://openapi.alipay.com/gateway.do?";
  3. //获取支付宝二维码的接口方法
  4. const API_METHOD_GENERATE_QR = 'alipay.open.app.qrcode.create';
  5. //获取token的接口方法
  6. const API_METHOD_AUTH_TOKEN = 'alipay.system.oauth.token';
  7. //获取用户信息的接口方法
  8. const API_METHOD_GET_USER_INFO = 'alipay.user.info.share';
  9. //支付宝的签名方式,由RSA2和RSA两种
  10. const SIGN_TYPE_RSA2 = 'RSA2';
  11. //版本号,此处固定挑那些就可以了
  12. const VERSION = '1.0';
  13. //UTF8编码
  14. const FILE_CHARSET_UTF8 = "UTF-8";
  15. //GBK编码
  16. const FILE_CHARSET_GBK = "GBK";
  17. //二维码接口调用成功的 返回节点
  18. const RESPONSE_OUTER_NODE_QR = 'alipay_open_app_qrcode_create_response';
  19. //token接口调用成功的 返回节点
  20. const RESPONSE_OUTER_NODE_AUTH_TOKEN = 'alipay_system_oauth_token_response';
  21. //用户信息接口调用成功的 返回节点
  22. const RESPONSE_OUTER_NODE_USER_INFO = 'alipay_user_info_share_response';
  23. //错误的返回的时候的节点
  24. const RESPONSE_OUTER_NODE_ERROR_RESPONSE = 'error_response';
  25. const STATUS_CODE_SUCCESS = 10000;
  26. const STATUS_CODE_EXCEPT = 20000;
复制代码
getAmpUserInfoByAuthCode方法
这个方法是获取用户信息的接口方法,只必要传入客户端传递的code,就可以获取到用户的完整信息
getAmpToken方法
这个方法是获取付出宝接口的token的方法,是一个公用方法,背面所有的付出宝的口调用,都可以利用这个方法先获取token
getResponse方法
考虑到会调用各个付出宝的接口,因此这里封装这个方法是为了方便截取接口返回乐成之后的信息,进步代码的阅读性
getApiPubParam方法
这个方法是为了获取公共的参数,包括版本号,编码,appid,署名范例等基础业务参数
getSignContent方法
这个方法是获取署名的内容,入参是一个数组,末了输出的是参数的拼接字符串
buildApiBuisinessParam($businessParam,$apiMethod)
这个是构建api独立的业务参数部分方法,businessParam参数是付出宝各个接口的业务参数部分(出去公共参数),$apiMethod是对应的接口的方法名称,如获取token的方法名为alipay.system.oauth.token
署名帮助类
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Auser
  5. * Date: 2018/12/4
  6. * Time: 15:37
  7. */
  8. namespace App\Http\Helper;
  9. /**
  10. *$rsa2 = new Rsa2();
  11. *$data = 'mydata'; //待签名字符串
  12. *$strSign = $rsa2->createSign($data);   //生成签名
  13. *$is_ok = $rsa2->verifySign($data, $strSign); //验证签名
  14. */
  15. class RsaHelper
  16. {
  17.   private static $PRIVATE_KEY;
  18.   private static $PUBLIC_KEY;
  19.   function __construct(){
  20.     self::$PRIVATE_KEY = config('param.amp.private_key');
  21.     self::$PUBLIC_KEY = config('param.amp.public_key');
  22.   }
  23.   /**
  24.    * 获取私钥
  25.    * @return bool|resource
  26.    */
  27.   private static function getPrivateKey()
  28.   {
  29.     $privKey = self::$PRIVATE_KEY;
  30.     $privKey = "-----BEGIN RSA PRIVATE KEY-----".PHP_EOL.wordwrap($privKey, 64, PHP_EOL, true).PHP_EOL."-----END RSA PRIVATE KEY-----";
  31.     ($privKey) or die('您使用的私钥格式错误,请检查RSA私钥配置');
  32.     error_log('private_key is ===========>: '.$privKey);
  33.     return openssl_pkey_get_private($privKey);
  34.   }
  35.   /**
  36.    * 获取公钥
  37.    * @return bool|resource
  38.    */
  39.   private static function getPublicKey()
  40.   {
  41.     $publicKey = self::$PUBLIC_KEY;
  42.     $publicKey = "-----BEGIN RSA PRIVATE KEY-----".PHP_EOL.wordwrap($publicKey, 64, PHP_EOL, true).PHP_EOL."-----END RSA PRIVATE KEY-----";
  43.     error_log('public key is : ===========>'.$publicKey);
  44.     return openssl_pkey_get_public($publicKey);
  45.   }
  46.   /**
  47.    * 创建签名
  48.    * @param string $data 数据
  49.    * @return null|string
  50.    */
  51.   public function createSign($data = '')
  52.   {
  53.     // var_dump(self::getPrivateKey());die;
  54.     if (!is_string($data)) {
  55.       return null;
  56.     }
  57.     return openssl_sign($data, $sign, self::getPrivateKey(),OPENSSL_ALGO_SHA256 ) ? base64_encode($sign) : null;
  58.   }
  59.   /**
  60.    * 验证签名
  61.    * @param string $data 数据
  62.    * @param string $sign 签名
  63.    * @return bool
  64.    */
  65.   public function verifySign($data = '', $sign = '')
  66.   {
  67.     if (!is_string($sign) || !is_string($sign)) {
  68.       return false;
  69.     }
  70.     return (bool)openssl_verify(
  71.       $data,
  72.       base64_decode($sign),
  73.       self::getPublicKey(),
  74.       OPENSSL_ALGO_SHA256
  75.     );
  76.   }
  77. }
复制代码
调用
  1. $originUserData = AmpHelper::getAmpUserInfoByAuthCode($code);
  2. echo $originUserData;
复制代码
注意getAmpUserInfoByAuthCode方法,调用接口乐成,会返回付出宝用户的正确信息,示例如下
  1. {
  2.   "alipay_user_info_share_response": {
  3.     "code": "10000",
  4.     "msg": "Success",
  5.     "user_id": "2088102104794936",
  6.     "avatar": "http://tfsimg.alipay.com/images/partner/T1uIxXXbpXXXXXXXX",
  7.     "province": "安徽省",
  8.     "city": "安庆",
  9.     "nick_name": "支付宝小二",
  10.     "is_student_certified": "T",
  11.     "user_type": "1",
  12.     "user_status": "T",
  13.     "is_certified": "T",
  14.     "gender": "F"
  15.   },
  16.   "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
  17. }
复制代码
踩坑点
      
  • 在开发之前一定要仔细阅读用户的授权流程指引文档,否则很容堕落  
  • 对于用户信息接口,在获取授权信息接口并没有做明白的说明,所以必要先梳理清晰  
  • 付出宝的署名机制和微信的有很大差别,对于风俗了微信小程序开发的人来说,刚开始大概有点不顺应,所以必要多看看sdk里面的实现
以上就是本文的全部内容,盼望对各人的学习有所帮助,也盼望各人多多支持草根技能分享。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作