霁风麦穗 發表於 2023-3-4 23:32:00

微信小程序结合php后台实现登录授权机制详解

<p>基本就是手机访问微信官方服务器获取到code用于标识用户,app服务器用该code和自己的appid和appsecrept来访问微信官方服务器,从而获得用户对于该app的唯一标识ID,即openid,然后app后台将此openid做成token,传给前台,供前台以后携带些token来证实自己身份。</p>
<p>微信小程序应用的用户登录授权机制相当复杂,官方给出了下面一张流程图来解释:</p>
<p><img src="https://img2023.cnblogs.com/blog/181178/202303/181178-20230304214521989-1093460135.png" alt=""></p>
<p>下面结合这张图来详细讲述下小程序的登录验证授权机制。</p>
<p>首先,小程序应用实现登录验证的前提是需要在微信开放平台注册一个开发者账号,申请到AppID 和 AppSecret。并申请开启“获取用户信息”的权限。</p>
<p>然后 ,<span style="font-style: italic">在小程序中使用微信提供的 wx.login 接口获取用户的临时登录凭证 code。代码示例如下:</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查用户是否已经授权</span>
<span style="color: rgba(0, 0, 0, 1)">wx.getSetting({
success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (res.authSetting['scope.userInfo'<span style="color: rgba(0, 0, 0, 1)">]) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户已经授权,可以直接调用 wx.getUserProfile 获取用户信息</span>
<span style="color: rgba(0, 0, 0, 1)">      wx.getUserProfile({
      desc: </span>'获取用户信息'<span style="color: rgba(0, 0, 0, 1)">,
      success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户信息</span>
          const userInfo =<span style="color: rgba(0, 0, 0, 1)"> res.userInfo;
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户登录凭证</span>
<span style="color: rgba(0, 0, 0, 1)">          wx.login({
            success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户登录凭证</span>
            const code =<span style="color: rgba(0, 0, 0, 1)"> res.code;

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将 code 和 userInfo 发送到后台服务器进行处理</span>
<span style="color: rgba(0, 0, 0, 1)">            wx.request({
                url: </span>'https://example.com/login.php'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  code: code,
                  userInfo: userInfo
                },
                method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
                success: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (res) {
                  console.log(res.data);
                }
            });
            }
          })
      }
      })
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户未授权,需要显示授权弹窗</span>
<span style="color: rgba(0, 0, 0, 1)">      wx.authorize({
      scope: </span>'scope.userInfo'<span style="color: rgba(0, 0, 0, 1)">,
      success: () </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户完成授权,可以继续获取用户信息</span>
<span style="color: rgba(0, 0, 0, 1)">          wx.getUserProfile({
            desc: </span>'获取用户信息'<span style="color: rgba(0, 0, 0, 1)">,
            success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户信息</span>
            const userInfo =<span style="color: rgba(0, 0, 0, 1)"> res.userInfo;
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户登录凭证</span>
<span style="color: rgba(0, 0, 0, 1)">            wx.login({
                success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取用户登录凭证</span>
                  const code =<span style="color: rgba(0, 0, 0, 1)"> res.code;
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将 code 和 userInfo 发送到后台服务器进行处理</span>
<span style="color: rgba(0, 0, 0, 1)">                  wx.request({
                  url: </span>'https://example.com/login.php'<span style="color: rgba(0, 0, 0, 1)">,
                  data: {
                      code: code,
                      userInfo: userInfo
                  },
                  method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
                  success: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (res) {
                      wx.setStorage({                        ​
                        key: </span>"token"<span style="color: rgba(0, 0, 0, 1)">,
                        ​data: res.data.token​
                      })
                      console.log(res.data);
                  }
                  });
                }
            })
            }
          })
      }
      })
    }
}
})</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>接着,小程序前台应用<span style="font-style: italic">将获取到的 code 发送给后台服务器进行登录验证。后台服务器需要使用微信提供的接口(例如 wx.login 和 wx.getUserInfo)通过 code 获取用户的唯一标识 OpenID 和会话密钥 session_key。然后</span><span style="font-style: italic">将用户的 OpenID 和 session_key 存储到后台数据库中,并将一个自定义的 token 返回给小程序前端。php代码示例如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)"><span class="hljs-variable">$appid = <span class="hljs-string">'your_appid'; <br><span class="hljs-variable">$appsecret = <span class="hljs-string">'your_appsecret'; <br><span class="hljs-variable">$code = <span class="hljs-variable">$_POST[<span class="hljs-string">'code']; <br><span class="hljs-variable">$url = <span class="hljs-string">"https://api.weixin.qq.com/sns/jscode2session?appid=<span class="hljs-subst">$appid&amp;secret=<span class="hljs-subst">$appsecret&amp;js_code=<span class="hljs-subst">$code&amp;grant_type=authorization_code"; </span></span></span></span></span></span></span></span></span></span></span></span></span><span style="color: rgba(128, 0, 128, 1)">$result</span> = <span style="color: rgba(0, 128, 128, 1)">file_get_contents</span>(<span style="color: rgba(128, 0, 128, 1)">$url</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$data</span> = json_decode(<span style="color: rgba(128, 0, 128, 1)">$result</span>, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$openid</span> = <span style="color: rgba(128, 0, 128, 1)">$data</span>['openid'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(128, 0, 128, 1)">$session_key</span> = <span style="color: rgba(128, 0, 128, 1)">$data</span>['session_key'<span style="color: rgba(0, 0, 0, 1)">];

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储到数据库中</span>
<span style="color: rgba(128, 0, 128, 1)">$db</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> mysqli('localhost', 'username', 'password', 'database'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$sql</span> = "INSERT INTO `user`(`openid`, `session_key`) VALUES ('{<span style="color: rgba(128, 0, 128, 1)">$openid</span>}', '{<span style="color: rgba(128, 0, 128, 1)">$session_key</span>}')"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$result</span> = <span style="color: rgba(128, 0, 128, 1)">$db</span>-&gt;query(<span style="color: rgba(128, 0, 128, 1)">$sql</span>);</pre>
</div>
<p>接下来,php根据openid和session_key再生成token,返还给前台小程序应用。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> generateToken(<span style="color: rgba(128, 0, 128, 1)">$openid</span>, <span style="color: rgba(128, 0, 128, 1)">$session_key</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$header</span> =<span style="color: rgba(0, 0, 0, 1)"> [
    </span>'typ' =&gt; 'JWT',
    'alg' =&gt; 'HS256'<span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(128, 0, 128, 1)">$payload</span> =<span style="color: rgba(0, 0, 0, 1)"> [
    </span>'openid' =&gt; <span style="color: rgba(128, 0, 128, 1)">$openid</span>,
    'iat' =&gt; <span style="color: rgba(0, 128, 128, 1)">time</span>(),
    'exp' =&gt; <span style="color: rgba(0, 128, 128, 1)">time</span>() + 3600<span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(128, 0, 128, 1)">$secret</span> = 'my_secret_key'<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 生成 JWT Token</span>
<span style="color: rgba(128, 0, 128, 1)">$base64UrlHeader</span> = base64UrlEncode(json_encode(<span style="color: rgba(128, 0, 128, 1)">$header</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(128, 0, 128, 1)">$base64UrlPayload</span> = base64UrlEncode(json_encode(<span style="color: rgba(128, 0, 128, 1)">$payload</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(128, 0, 128, 1)">$signature</span> = hash_hmac('sha256', <span style="color: rgba(128, 0, 128, 1)">$base64UrlHeader</span> . '.' . <span style="color: rgba(128, 0, 128, 1)">$base64UrlPayload</span>, <span style="color: rgba(128, 0, 128, 1)">$secret</span>, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$base64UrlSignature</span> = base64UrlEncode(<span style="color: rgba(128, 0, 128, 1)">$signature</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$token</span> = <span style="color: rgba(128, 0, 128, 1)">$base64UrlHeader</span> . '.' . <span style="color: rgba(128, 0, 128, 1)">$base64UrlPayload</span> . '.' . <span style="color: rgba(128, 0, 128, 1)">$base64UrlSignature</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">;
}

</span><span style="color: rgba(0, 0, 255, 1)">function</span> base64UrlEncode(<span style="color: rgba(128, 0, 128, 1)">$data</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$urlSafeData</span> = <span style="color: rgba(0, 128, 128, 1)">strtr</span>(<span style="color: rgba(0, 128, 128, 1)">base64_encode</span>(<span style="color: rgba(128, 0, 128, 1)">$data</span>), '+/', '-_'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">rtrim</span>(<span style="color: rgba(128, 0, 128, 1)">$urlSafeData</span>, '='<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(128, 0, 128, 1)">$token</span> = generateToken(<span style="color: rgba(128, 0, 128, 1)">$openid</span>, <span style="color: rgba(128, 0, 128, 1)">$session_key</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">echo</span> json_encode(['token' =&gt; <span style="color: rgba(128, 0, 128, 1)">$token</span>]);</pre>
</div>
<p>前台小程序应用接收到后台返回的token后,将其存储在小程序的本地缓存中,以后每次用户打开小程序时,我们可以从本地缓存中读取token并发送给后台进行验证。</p>
<p>如下示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取本地缓存中的token</span>
const token = wx.getStorageSync('token'<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果本地缓存中存在token,则向后台发送请求获取用户信息</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (token) {
wx.request({
    url: </span>'https://example.com/userinfo'<span style="color: rgba(0, 0, 0, 1)">,
    header: {
      </span>'Authorization'<span style="color: rgba(0, 0, 0, 1)">: `Bearer ${token}`
    },
    success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理响应结果</span>
<span style="color: rgba(0, 0, 0, 1)">      console.log(res.data);
    }
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果本地缓存中不存在token,则说明用户还未登录,需要重新登录获取token</span>
<span style="color: rgba(0, 0, 0, 1)">wx.login({
    success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向后台发送code,获取token</span>
<span style="color: rgba(0, 0, 0, 1)">      wx.request({
      url: </span>'https://example.com/login'<span style="color: rgba(0, 0, 0, 1)">,
      data: {
          code: res.code
      },
      success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将后台返回的token存储到本地缓存中</span>
          wx.setStorageSync('token'<span style="color: rgba(0, 0, 0, 1)">, res.data.token);
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向后台发送请求获取用户信息</span>
<span style="color: rgba(0, 0, 0, 1)">          wx.request({
            url: </span>'https://example.com/userinfo'<span style="color: rgba(0, 0, 0, 1)">,
            header: {
            </span>'Authorization'<span style="color: rgba(0, 0, 0, 1)">: `Bearer ${res.data.token}`
            },
            success: res </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理响应结果</span>
<span style="color: rgba(0, 0, 0, 1)">            console.log(res.data);
            }
          });
      }
      });
    }
});
}</span></pre>
</div>
<p>php后台获取到前台发来的请求及token后,就直接根据token来验证用户,如下代码所示:</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">use</span><span style="color: rgba(0, 0, 0, 1)"> \Firebase\JWT\JWT;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> JWT secret key</span>
<span style="color: rgba(128, 0, 128, 1)">$secret_key</span> = "your_secret_key"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Token received from front-end$secret_key和$token如存在数据库中,则从数据库中获取 </span>
<span style="color: rgba(128, 0, 128, 1)">$token</span> = "token_received_from_front_end"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Decode token and get the payload</span>
    <span style="color: rgba(128, 0, 128, 1)">$payload</span> = JWT::decode(<span style="color: rgba(128, 0, 128, 1)">$token</span>, <span style="color: rgba(128, 0, 128, 1)">$secret_key</span>, <span style="color: rgba(0, 0, 255, 1)">array</span>('HS256'<span style="color: rgba(0, 0, 0, 1)">));
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> You can now access the token payload data</span>
    <span style="color: rgba(128, 0, 128, 1)">$user_id</span> = <span style="color: rgba(128, 0, 128, 1)">$payload</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">user_id;
    </span><span style="color: rgba(128, 0, 128, 1)">$username</span> = <span style="color: rgba(128, 0, 128, 1)">$payload</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">username;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...

    // Token is valid</span>
    <span style="color: rgba(0, 0, 255, 1)">echo</span> "Token is valid!"<span style="color: rgba(0, 0, 0, 1)">;
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span> (<span style="color: rgba(0, 0, 255, 1)">Exception</span> <span style="color: rgba(128, 0, 128, 1)">$e</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Token is invalid</span>
    <span style="color: rgba(0, 0, 255, 1)">echo</span> "Token is invalid: " . <span style="color: rgba(128, 0, 128, 1)">$e</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">getMessage();
}
</span>?&gt;</pre>
</div>
<p>如此,则完成整个登录验证机制的实现。</p>
<div>&nbsp;</div><br><br>
来源:https://www.cnblogs.com/eminer/p/17179508.html
頁: [1]
查看完整版本: 微信小程序结合php后台实现登录授权机制详解