本文目录导读:

我来详细介绍PHP项目对接扫码登录接口的完整流程。
扫码登录基本原理
用户手机扫码 → 获取二维码信息 → 确认登录 → 服务器验证 → 登录成功
主流对接方式
微信扫码登录
<?php
class WechatScanLogin {
private $appId;
private $appSecret;
public function __construct($appId, $appSecret) {
$this->appId = $appId;
$this->appSecret = $appSecret;
}
// 获取二维码URL
public function getQrCodeUrl($state = '') {
$redirectUri = urlencode('http://yourdomain.com/callback.php');
$url = "https://open.weixin.qq.com/connect/qrconnect";
$url .= "?appid=" . $this->appId;
$url .= "&redirect_uri=" . $redirectUri;
$url .= "&response_type=code";
$url .= "&scope=snsapi_login";
$url .= "&state=" . $state;
$url .= "#wechat_redirect";
return $url;
}
// 回调处理
public function callback($code) {
// 获取access_token
$tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
$tokenUrl .= "?appid=" . $this->appId;
$tokenUrl .= "&secret=" . $this->appSecret;
$tokenUrl .= "&code=" . $code;
$tokenUrl .= "&grant_type=authorization_code";
$response = $this->httpGet($tokenUrl);
$data = json_decode($response, true);
if (isset($data['access_token'])) {
// 获取用户信息
$userInfo = $this->getUserInfo($data['access_token'], $data['openid']);
return $userInfo;
}
return false;
}
// 获取用户信息
private function getUserInfo($accessToken, $openid) {
$url = "https://api.weixin.qq.com/sns/userinfo";
$url .= "?access_token=" . $accessToken;
$url .= "&openid=" . $openid;
$response = $this->httpGet($url);
return json_decode($response, true);
}
// HTTP GET请求
private function httpGet($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
支付宝扫码登录
<?php
require_once 'vendor/autoload.php'; // 引入支付宝SDK
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
class AlipayScanLogin {
private $config;
public function __construct($appId, $privateKey, $alipayPublicKey) {
$this->config = new Config();
$this->config->protocol = 'https';
$this->config->gatewayHost = 'openapi.alipay.com';
$this->config->signType = 'RSA2';
$this->config->appId = $appId;
$this->config->merchantPrivateKey = $privateKey;
$this->config->alipayPublicKey = $alipayPublicKey;
Factory::setOptions($this->config);
}
// 生成扫码登录页面
public function getLoginPage() {
// 生成state参数防止CSRF
$state = bin2hex(random_bytes(16));
$_SESSION['alipay_state'] = $state;
$redirectUri = urlencode('http://yourdomain.com/alipay_callback.php');
$url = "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm";
$url .= "?app_id=" . $this->config->appId;
$url .= "&scope=auth_user";
$url .= "&redirect_uri=" . $redirectUri;
$url .= "&state=" . $state;
return $url;
}
// 处理回调
public function handleCallback($authCode, $state) {
// 验证state
if ($state !== $_SESSION['alipay_state']) {
throw new Exception('Invalid state parameter');
}
try {
// 获取access_token
$result = Factory::openAuth()->token()->get($authCode);
$accessToken = $result->accessToken;
// 获取用户信息
$userResult = Factory::openAuth()->user()->get();
return $userResult;
} catch (Exception $e) {
error_log('Alipay login error: ' . $e->getMessage());
return false;
}
}
}
自建扫码登录系统
<?php
class CustomScanLogin {
private $db;
private $redis;
public function __construct($db, $redis) {
$this->db = $db;
$this->redis = $redis;
}
// 生成二维码
public function generateQRCode() {
// 生成唯一标识
$ticket = $this->generateTicket();
$expireTime = 300; // 5分钟有效期
// 保存到Redis
$this->redis->setex("scan_login:{$ticket}", $expireTime, json_encode([
'status' => 'pending',
'created_at' => time()
]));
// 生成二维码内容
$qrContent = json_encode([
'type' => 'scan_login',
'ticket' => $ticket,
'timestamp' => time()
]);
// 使用QR码库生成二维码图片
$this->generateQRCodeImage($qrContent);
return [
'ticket' => $ticket,
'expire_in' => $expireTime
];
}
// 扫码验证(手机端调用)
public function scanQRCode($ticket, $userId) {
$loginData = $this->redis->get("scan_login:{$ticket}");
if (!$loginData) {
return ['error' => '二维码已过期'];
}
$loginData = json_decode($loginData, true);
if ($loginData['status'] !== 'pending') {
return ['error' => '二维码已被使用'];
}
// 生成临时token
$tempToken = bin2hex(random_bytes(32));
// 更新状态
$this->redis->setex("scan_login:{$ticket}", 60, json_encode([
'status' => 'scanned',
'user_id' => $userId,
'temp_token' => $tempToken,
'created_at' => $loginData['created_at']
]));
return [
'success' => true,
'temp_token' => $tempToken
];
}
// 确认登录(手机端调用)
public function confirmLogin($ticket, $tempToken) {
$loginData = $this->redis->get("scan_login:{$ticket}");
if (!$loginData) {
return ['error' => '登录已过期'];
}
$loginData = json_decode($loginData, true);
if ($loginData['temp_token'] !== $tempToken) {
return ['error' => '验证失败'];
}
// 生成登录token
$loginToken = bin2hex(random_bytes(32));
// 更新状态
$this->redis->setex("scan_login:{$ticket}", 60, json_encode([
'status' => 'confirmed',
'login_token' => $loginToken,
'user_id' => $loginData['user_id']
]));
return [
'success' => true,
'login_token' => $loginToken
];
}
// PC端轮询检查(轮询)
public function checkLoginStatus($ticket) {
$loginData = $this->redis->get("scan_login:{$ticket}");
if (!$loginData) {
return ['status' => 'expired'];
}
$loginData = json_decode($loginData, true);
switch ($loginData['status']) {
case 'pending':
return ['status' => 'pending'];
case 'scanned':
return ['status' => 'scanned'];
case 'confirmed':
// 清除Redis中的记录
$this->redis->del("scan_login:{$ticket}");
return [
'status' => 'confirmed',
'login_token' => $loginData['login_token']
];
default:
return ['status' => 'error'];
}
}
// 生成唯一ticket
private function generateTicket() {
return bin2hex(random_bytes(16));
}
// 生成二维码图片
private function generateQRCodeImage($content) {
// 使用phpqrcode库
require_once 'phpqrcode/qrlib.php';
QRcode::png($content, false, QR_ECLEVEL_L, 4);
}
}
前端实现
<!-- PC端扫码页面 -->
<!DOCTYPE html>
<html>
<head>扫码登录</title>
<style>
.qr-container {
width: 300px;
margin: 100px auto;
text-align: center;
}
#qr-code {
width: 200px;
height: 200px;
}
.status-text {
margin-top: 20px;
color: #666;
}
</style>
</head>
<body>
<div class="qr-container">
<h2>扫码登录</h2>
<div id="qr-code"></div>
<p class="status-text" id="status-text">请使用手机扫码</p>
<button onclick="refreshQRCode()" id="refresh-btn">刷新二维码</button>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
let pollTimer;
let currentTicket;
// 初始化
$(document).ready(function() {
refreshQRCode();
});
// 刷新二维码
function refreshQRCode() {
$.ajax({
url: '/api/generate_qr.php',
method: 'GET',
success: function(response) {
currentTicket = response.ticket;
$('#qr-code').html('<img src="api/qr_image.php?ticket=' + response.ticket + '" width="200" height="200">');
$('#status-text').text('请使用手机扫码');
startPolling();
},
error: function() {
$('#status-text').text('生成二维码失败');
}
});
}
// 开始轮询
function startPolling() {
if (pollTimer) {
clearInterval(pollTimer);
}
pollTimer = setInterval(function() {
$.ajax({
url: '/api/check_login.php?ticket=' + currentTicket,
method: 'GET',
success: function(response) {
switch(response.status) {
case 'pending':
$('#status-text').text('请使用手机扫码');
break;
case 'scanned':
$('#status-text').text('请在手机上确认登录');
break;
case 'confirmed':
$('#status-text').text('登录成功!');
clearInterval(pollTimer);
// 处理登录成功
handleLoginSuccess(response.login_token);
break;
case 'expired':
$('#status-text').text('二维码已过期');
clearInterval(pollTimer);
refreshQRCode();
break;
}
}
});
}, 2000); // 每2秒轮询一次
}
// 登录成功处理
function handleLoginSuccess(token) {
// 存储token到cookie或localStorage
document.cookie = "login_token=" + token + "; path=/";
// 跳转到首页
setTimeout(function() {
window.location.href = '/dashboard.php';
}, 1000);
}
</script>
</body>
</html>
API接口实现
<?php
// generate_qr.php - 生成二维码接口
header('Content-Type: application/json');
require_once 'config.php';
require_once 'ScanLogin.php';
$scanLogin = new CustomScanLogin($db, $redis);
$result = $scanLogin->generateQRCode();
echo json_encode($result);
// qr_image.php - 二维码图片接口
$ticket = $_GET['ticket'];
// 生成并返回二维码图片
// ...
// check_login.php - 检查登录状态接口
$ticket = $_GET['ticket'];
$scanLogin = new CustomScanLogin($db, $redis);
$result = $scanLogin->checkLoginStatus($ticket);
echo json_encode($result);
安全性建议
防止CSRF攻击
// 生成和验证state参数 $state = bin2hex(random_bytes(16)); $_SESSION['oauth_state'] = $state;
Token安全存储
// 使用HttpOnly Cookie存储token
setcookie('login_token', $token, [
'expires' => time() + 3600,
'path' => '/',
'domain' => 'yourdomain.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
请求频率限制
// 使用Redis限制轮询频率
$ip = $_SERVER['REMOTE_ADDR'];
$key = "rate_limit:{$ip}:scan_login";
$count = $redis->incr($key);
if ($count > 10) {
$redis->expire($key, 60);
die(json_encode(['error' => '请求过于频繁']));
}
$redis->expire($key, 60);
最佳实践
- 二维码有效期:建议5分钟内有效
- 轮询间隔:建议2-3秒轮询一次
- 错误处理:网络异常时自动重试
- 用户体验:显示扫码状态变化动画
- 安全防护:添加验证码防止暴力破解
这个实现方案可以满足大多数扫码登录场景的需求,你可以根据具体业务需求进行调整。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播