ThinkPHP项目微信登录与授权

wen PHP项目 3

本文目录导读:

ThinkPHP项目微信登录与授权

  1. 基础准备
  2. 网页授权登录
  3. 用户登录处理
  4. 移动端H5登录
  5. 扫码登录(网站应用)
  6. 小程序登录
  7. 数据库设计
  8. 安全措施
  9. 前端集成
  10. 注意事项

我来详细介绍ThinkPHP项目中实现微信登录与授权的完整方案。

基础准备

环境配置

// config/wechat.php 配置文件
return [
    'app_id' => 'your_app_id',
    'app_secret' => 'your_app_secret',
    'redirect_uri' => 'https://yourdomain.com/wechat/callback',
    'scope' => 'snsapi_userinfo', // snsapi_base 或 snsapi_userinfo
    'oauth_url' => 'https://open.weixin.qq.com/connect/oauth2/authorize',
    'token_url' => 'https://api.weixin.qq.com/sns/oauth2/access_token',
    'userinfo_url' => 'https://api.weixin.qq.com/sns/userinfo'
];

安装依赖

composer require overtrue/wechat

网页授权登录

发起授权请求

<?php
namespace app\wechat\controller;
use think\Controller;
use think\facade\Config;
use think\facade\Session;
class Login extends Controller
{
    protected $wechatConfig;
    public function __construct()
    {
        $this->wechatConfig = Config::get('wechat.');
    }
    // 发起微信授权
    public function authorize()
    {
        // 生成随机state参数防止CSRF
        $state = md5(uniqid(rand(), true));
        Session::set('wechat_state', $state);
        $param = [
            'appid' => $this->wechatConfig['app_id'],
            'redirect_uri' => $this->wechatConfig['redirect_uri'],
            'response_type' => 'code',
            'scope' => $this->wechatConfig['scope'],
            'state' => $state
        ];
        $url = $this->wechatConfig['oauth_url'] . '?' . http_build_query($param) . '#wechat_redirect';
        return redirect($url);
    }
}

回调处理

// 回调处理
public function callback()
{
    $code = input('get.code');
    $state = input('get.state');
    // 验证state防止CSRF
    if ($state !== Session::get('wechat_state')) {
        return json(['code' => -1, 'msg' => '非法请求']);
    }
    // 获取access_token
    $tokenInfo = $this->getAccessToken($code);
    if (isset($tokenInfo['errcode'])) {
        return json(['code' => -1, 'msg' => $tokenInfo['errmsg']]);
    }
    // 获取用户信息
    $userInfo = $this->getUserInfo($tokenInfo);
    // 处理用户登录
    $result = $this->processLogin($tokenInfo, $userInfo);
    return $result;
}
// 获取access_token
private function getAccessToken($code)
{
    $url = $this->wechatConfig['token_url'];
    $params = [
        'appid' => $this->wechatConfig['app_id'],
        'secret' => $this->wechatConfig['app_secret'],
        'code' => $code,
        'grant_type' => 'authorization_code'
    ];
    $response = Http::get($url, $params);
    return json_decode($response, true);
}
// 获取用户信息
private function getUserInfo($tokenInfo)
{
    $url = $this->wechatConfig['userinfo_url'];
    $params = [
        'access_token' => $tokenInfo['access_token'],
        'openid' => $tokenInfo['openid'],
        'lang' => 'zh_CN'
    ];
    $response = Http::get($url, $params);
    return json_decode($response, true);
}

用户登录处理

业务逻辑处理

// 处理用户登录
private function processLogin($tokenInfo, $userInfo)
{
    $openid = $userInfo['openid'];
    // 检查用户是否存在
    $user = UsersModel::where('openid', $openid)->find();
    if (!$user) {
        // 新用户注册
        $userData = [
            'openid' => $openid,
            'nickname' => $userInfo['nickname'] ?? '',
            'avatar' => $userInfo['headimgurl'] ?? '',
            'sex' => $userInfo['sex'] ?? 0,
            'province' => $userInfo['province'] ?? '',
            'city' => $userInfo['city'] ?? '',
            'unionid' => $tokenInfo['unionid'] ?? '',
            'create_time' => time()
        ];
        $userId = UsersModel::insertGetId($userData);
        $user = UsersModel::find($userId);
    } else {
        // 更新用户信息
        $userData = [
            'nickname' => $userInfo['nickname'] ?? $user['nickname'],
            'avatar' => $userInfo['headimgurl'] ?? $user['avatar'],
            'update_time' => time()
        ];
        UsersModel::where('id', $user['id'])->update($userData);
        $user = UsersModel::find($user['id']);
    }
    // 生成登录凭证
    $token = $this->createToken($user['id']);
    // 记录登录日志
    $this->saveLoginLog($user['id']);
    // 返回登录信息
    return json([
        'code' => 1,
        'msg' => '登录成功',
        'data' => [
            'token' => $token,
            'user' => $user
        ]
    ]);
}
// 生成登录token
private function createToken($userId)
{
    $payload = [
        'user_id' => $userId,
        'iat' => time(),
        'exp' => time() + 7200 // 有效期2小时
    ];
    $token = base64_encode(json_encode($payload));
    // 保存到缓存
    Cache::set('login_token_' . $token, $userId, 7200);
    return $token;
}

移动端H5登录

namespace app\wechat\controller;
use think\Controller;
use Overtrue\WeChat\Factory;
class MobileLogin extends Controller
{
    protected $app;
    public function __construct()
    {
        $this->app = Factory::officialAccount([
            'app_id' => config('wechat.app_id'),
            'secret' => config('wechat.app_secret'),
            'response_type' => 'array',
            'log' => [
                'level' => 'debug',
                'file' => runtime_path() . '/wechat.log',
            ],
        ]);
    }
    // 微信授权登录
    public function oauthLogin()
    {
        $oauth = $this->app->oauth;
        // 设置回调地址
        $callbackUrl = url('wechat/MobileLogin/oauthCallback', [], true, true);
        // 跳转到授权页面
        return redirect($oauth->redirect($callbackUrl));
    }
    // 回调函数
    public function oauthCallback()
    {
        $oauth = $this->app->oauth;
        // 获取OAuth用户信息
        $user = $oauth->user();
        // 获取用户基本信息
        $userInfo = $user->toArray();
        // 处理登录逻辑
        return $this->processOauthLogin($userInfo);
    }
}

扫码登录(网站应用)

生成二维码

namespace app\wechat\controller;
class QrLogin extends Controller
{
    // 获取登录二维码
    public function getQrCode()
    {
        $state = md5(rand());
        Session::set('qr_state', $state);
        // 生成二维码内容
        $qrContent = url('wechat/QrLogin/qrCallback', [
            'state' => $state
        ], true, true);
        // 生成二维码
        $qrCode = new QrCode($qrContent);
        // 返回二维码图片
        return $qrCode->setSize(220)->setMargin(10)->writeString();
    }
    // 轮询检查登录状态
    public function checkLogin()
    {
        $state = input('state');
        $key = 'qr_login_' . $state;
        if (Cache::has($key)) {
            $userInfo = Cache::get($key);
            Cache::delete($key);
            // 完成登录
            return json([
                'code' => 1,
                'msg' => '登录成功',
                'data' => $userInfo
            ]);
        }
        return json([
            'code' => 0,
            'msg' => '等待扫码'
        ]);
    }
}

小程序登录

namespace app\wechat\controller;
use think\Controller;
use think\facade\Cache;
class MiniProgram extends Controller
{
    protected $config;
    public function __construct()
    {
        $this->config = [
            'app_id' => config('wechat.mini_app_id'),
            'app_secret' => config('wechat.mini_app_secret'),
        ];
    }
    // 小程序登录
    public function login()
    {
        $code = input('code');
        $userInfo = input('userInfo/a', []);
        // 获取微信登录凭证
        $sessionData = $this->code2Session($code);
        if (isset($sessionData['errcode'])) {
            return json(['code' => -1, 'msg' => $sessionData['errmsg']]);
        }
        $openid = $sessionData['openid'];
        $unionid = $sessionData['unionid'] ?? '';
        // 查询或创建用户
        $user = $this->findOrCreateUser($openid, $unionid, $userInfo);
        // 生成登录凭证
        $token = $this->createLoginToken($user['id']);
        return json([
            'code' => 1,
            'msg' => '登录成功',
            'data' => [
                'token' => $token,
                'user_id' => $user['id']
            ]
        ]);
    }
    // code换session
    private function code2Session($code)
    {
        $url = 'https://api.weixin.qq.com/sns/jscode2session';
        $params = [
            'appid' => $this->config['app_id'],
            'secret' => $this->config['app_secret'],
            'js_code' => $code,
            'grant_type' => 'authorization_code'
        ];
        $response = Http::get($url, $params);
        return json_decode($response, true);
    }
}

数据库设计

-- 用户表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `openid` varchar(50) NOT NULL COMMENT '微信openid',
  `unionid` varchar(50) DEFAULT NULL COMMENT '微信unionid',
  `nickname` varchar(100) DEFAULT NULL COMMENT '昵称',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `sex` tinyint(4) DEFAULT '0' COMMENT '性别 0未知 1男 2女',
  `province` varchar(50) DEFAULT NULL COMMENT '省份',
  `city` varchar(50) DEFAULT NULL COMMENT '城市',
  `country` varchar(50) DEFAULT NULL COMMENT '国家',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `status` tinyint(4) DEFAULT '1' COMMENT '状态 1正常 0禁用',
  `last_login_time` int(11) DEFAULT NULL COMMENT '最后登录时间',
  `create_time` int(11) DEFAULT NULL COMMENT '创建时间',
  `update_time` int(11) DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 登录日志表
CREATE TABLE `user_login_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `login_type` varchar(20) DEFAULT 'wechat' COMMENT '登录类型',
  `ip` varchar(50) DEFAULT NULL COMMENT '登录IP',
  `user_agent` varchar(255) DEFAULT NULL COMMENT '浏览器信息',
  `create_time` int(11) DEFAULT NULL COMMENT '登录时间',
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

安全措施

// 安全防护示例
class Security implements Middleware
{
    public function handle($request, \Closure $next)
    {
        // 1. 验证请求时间戳
        $timestamp = $request->header('timestamp');
        if (abs(time() - $timestamp) > 300) {
            return json(['code' => -1, 'msg' => '请求已过期']);
        }
        // 2. 验证签名
        $sign = $request->header('sign');
        $params = $request->param();
        ksort($params);
        $signStr = http_build_query($params) . '&key=' . config('wechat.sign_key');
        if (md5($signStr) !== $sign) {
            return json(['code' => -1, 'msg' => '签名验证失败']);
        }
        // 3. 防止重放攻击
        $nonce = $request->header('nonce');
        if (Cache::has($nonce)) {
            return json(['code' => -1, 'msg' => '重复请求']);
        }
        Cache::set($nonce, time(), 300);
        return $next($request);
    }
}

前端集成

<!-- 前端登录按钮 -->
<script>
// 微信登录
function wechatLogin() {
    window.location.href = '/wechat/Login/authorize';
}
// 获取登录状态
function checkLoginStatus() {
    $.ajax({
        url: '/user/check-login',
        method: 'GET',
        success: function(response) {
            if (response.code === 1) {
                // 已登录
                showUserInfo(response.data.user);
            }
        }
    });
}
// 保存token到本地
function saveToken(token) {
    localStorage.setItem('wechat_token', token);
}
// 请求时携带token
$.ajaxSetup({
    beforeSend: function(xhr) {
        const token = localStorage.getItem('wechat_token');
        if (token) {
            xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        }
    }
});
</script>

注意事项

  1. 安全方面

    • 使用HTTPS协议
    • 验证state参数防止CSRF
    • 对敏感信息进行加密存储
    • 限制回调地址白名单
  2. 开发环境

    • 内网穿透工具:ngrok、natapp等
    • 配置域名白名单
    • 使用测试号进行开发测试
  3. 生产环境

    • 配置正式AppID和AppSecret
    • 服务器IP白名单
    • 日志监控和错误告警

这个方案涵盖了微信网页授权、小程序登录、扫码登录等场景,可根据实际需求选择使用。

抱歉,评论功能暂时关闭!