核心功能
- Token 生成 / 验证 / 刷新(访问 Token 有效期 2 小时,刷新 Token 有效期 7 天)
- 签名防篡改(基于 HMAC-SHA256 + 时间戳 + Nonce,防止重放攻击)
- 完整请求日志记录(含 IP、参数、Token 状态,支持自动清理)
- 动态签名密钥机制(避免前端硬编码密钥泄露)
- Token 过期自动刷新并重试请求
- 安全加固措施(强制 HTTPS、参数过滤、权限管控)
核心插件文件:wp-weapp-token.php
<?php
/*
Plugin Name: WordPress 微信小程序 Token 管理
Plugin URI: https://example.com/
Description: 实现微信小程序登录、Token生成/验证/刷新、签名防篡改、请求日志
Version: 1.0
Author: 自定义
License: GPL2
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// ====================== 1. 全局配置 ======================
// 从wp-config.php读取密钥(建议在wp-config.php中定义:define('WEAPP_SIGN_SECRET', '你的32位密钥');)
if (!defined('WEAPP_SIGN_SECRET')) {
define('WEAPP_SIGN_SECRET', '默认32位随机密钥(上线前替换)');
}
// 小程序AppID/Secret(建议在wp-config.php中定义)
if (!defined('WEAPP_APPID')) {
define('WEAPP_APPID', '你的小程序AppID');
}
if (!defined('WEAPP_SECRET')) {
define('WEAPP_SECRET', '你的小程序AppSecret');
}
// ====================== 2. 插件安装/卸载 ======================
// 安装时创建数据库表
register_activation_hook(__FILE__, 'weapp_token_install');
function weapp_token_install() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$prefix = $wpdb->prefix;
// 1. Token表
$token_table = "{$prefix}weapp_token";
$token_sql = "CREATE TABLE {$token_table} (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
user_id bigint(20) UNSIGNED NOT NULL COMMENT 'WP用户ID',
openid varchar(128) NOT NULL COMMENT '小程序OpenID',
unionid varchar(128) DEFAULT '' COMMENT 'UnionID',
access_token varchar(256) NOT NULL COMMENT '访问Token',
refresh_token varchar(256) NOT NULL COMMENT '刷新Token',
session_key varchar(128) NOT NULL COMMENT '微信session_key',
sign_secret varchar(64) NOT NULL COMMENT '动态签名密钥',
access_expire int(11) NOT NULL COMMENT '访问Token过期时间',
refresh_expire int(11) NOT NULL COMMENT '刷新Token过期时间',
created_at int(11) NOT NULL,
updated_at int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY access_token (access_token),
UNIQUE KEY refresh_token (refresh_token),
KEY user_id (user_id),
KEY openid (openid)
) {$charset_collate};";
// 2. 请求日志表
$log_table = "{$prefix}weapp_request_log";
$log_sql = "CREATE TABLE {$log_table} (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
user_id bigint(20) UNSIGNED DEFAULT 0 COMMENT 'WP用户ID',
openid varchar(128) DEFAULT '' COMMENT '小程序OpenID',
api_path varchar(256) NOT NULL COMMENT '接口路径',
request_method varchar(16) NOT NULL COMMENT '请求方法',
request_params text DEFAULT '' COMMENT '请求参数JSON',
request_ip varchar(64) DEFAULT '' COMMENT '请求IP',
request_time int(11) NOT NULL COMMENT '请求时间戳',
response_code int(11) NOT NULL COMMENT '响应业务码',
response_msg varchar(256) DEFAULT '' COMMENT '响应信息',
token_status varchar(32) DEFAULT '' COMMENT 'Token状态',
PRIMARY KEY (id),
KEY user_id (user_id),
KEY api_path (api_path),
KEY request_time (request_time)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($token_sql);
dbDelta($log_sql);
// 添加定时任务:每天清理30天前的日志
if (!wp_next_scheduled('weapp_clean_old_logs')) {
wp_schedule_event(time(), 'daily', 'weapp_clean_old_logs');
}
}
// 定时清理日志
add_action('weapp_clean_old_logs', 'weapp_clean_old_logs');
function weapp_clean_old_logs() {
global $wpdb;
$log_table = $wpdb->prefix . 'weapp_request_log';
$expire_time = time() - 30 * 86400; // 30天前
$wpdb->delete($log_table, ['request_time <' => $expire_time], ['%d']);
}
// ====================== 3. 核心工具函数 ======================
/**
* 生成安全随机Token
* @param int $length 长度
* @return string
*/
function weapp_generate_token($length = 32) {
$random_bytes = openssl_random_pseudo_bytes($length);
return $random_bytes ? bin2hex($random_bytes) : substr(md5(uniqid(mt_rand(), true) . time()), 0, $length);
}
/**
* 微信code2Session接口调用
* @param string $code 小程序登录code
* @return array|false
*/
function weapp_code2session($code) {
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . WEAPP_APPID . "&secret=" . WEAPP_SECRET . "&js_code={$code}&grant_type=authorization_code";
$response = wp_remote_get($url, ['timeout' => 10]);
if (is_wp_error($response)) return false;
$data = json_decode(wp_remote_retrieve_body($response), true);
return isset($data['errcode']) && $data['errcode'] != 0 ? false : $data;
}
/**
* 验证请求签名
* @param array $request_data 请求参数
* @param string $access_token 访问Token
* @param string $sign_secret 动态签名密钥
* @return bool
*/
function weapp_verify_sign($request_data, $access_token, $sign_secret) {
// 过滤空值+统一数据类型
$clean_data = [];
foreach ($request_data as $key => $value) {
if ($value !== '' && $value !== null && $value !== undefined) {
$clean_data[$key] = (string)$value;
}
}
// 检查必要参数
if (!isset($clean_data['sign']) || !isset($clean_data['timestamp']) || !isset($clean_data['nonce'])) {
return false;
}
$sign = $clean_data['sign'];
$timestamp = $clean_data['timestamp'];
$nonce = $clean_data['nonce'];
// 验证时间戳(2分钟有效期)
if (abs(time() - $timestamp) > 120) {
return false;
}
// 验证Nonce(防止重复使用)
$cache_key = 'weapp_nonce_' . $nonce;
if (wp_cache_get($cache_key)) return false;
wp_cache_set($cache_key, 1, '', 120);
// 生成签名对比
unset($clean_data['sign']);
$sign_arr = array_merge($clean_data, [
'timestamp' => (string)$timestamp,
'nonce' => (string)$nonce,
'secret' => $sign_secret,
'token' => (string)$access_token
]);
ksort($sign_arr, SORT_STRING | SORT_FLAG_CASE);
$sign_str = http_build_query($sign_arr);
$generate_sign = hash_hmac('sha256', $sign_str, WEAPP_SIGN_SECRET); // HMAC-SHA256
return strtolower($sign) === strtolower($generate_sign);
}
/**
* 记录请求日志
* @param array $log_data 日志数据
*/
function weapp_log_request($log_data) {
global $wpdb;
$log_table = $wpdb->prefix . 'weapp_request_log';
$log_data = wp_parse_args($log_data, [
'user_id' => 0,
'openid' => '',
'api_path' => '',
'request_method' => '',
'request_params' => '',
'request_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'request_time' => time(),
'response_code' => -1,
'response_msg' => '',
'token_status' => ''
]);
if (is_array($log_data['request_params'])) {
$log_data['request_params'] = json_encode($log_data['request_params'], JSON_UNESCAPED_UNICODE);
}
$wpdb->insert($log_table, $log_data, ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s']);
}
/**
* 绑定用户并生成Token
* @param string $openid OpenID
* @param string $session_key session_key
* @param string $unionid UnionID
* @return array|false
*/
function weapp_bind_user_and_token($openid, $session_key, $unionid = '') {
global $wpdb;
$token_table = $wpdb->prefix . 'weapp_token';
// 查找已有用户
$token_info = $wpdb->get_row($wpdb->prepare("SELECT user_id FROM {$token_table} WHERE openid = %s", $openid), ARRAY_A);
$user_id = $token_info ? $token_info['user_id'] : 0;
// 新建用户(无关联时)
if (!$user_id) {
$username = 'weapp_' . substr(md5($openid), 0, 10);
$user_data = [
'user_login' => $username,
'user_pass' => wp_generate_password(16),
'user_nicename' => '小程序用户',
'display_name' => '小程序用户',
'user_status' => 1,
'role' => 'subscriber'
];
$user_id = wp_insert_user($user_data);
if (is_wp_error($user_id)) return false;
}
// 生成Token和动态签名密钥
$access_token = weapp_generate_token(32);
$refresh_token = weapp_generate_token(64);
$sign_secret = weapp_generate_token(16);
$access_expire = time() + 7200; // 2小时
$refresh_expire = time() + 604800; // 7天
// 存储Token
$wpdb->delete($token_table, ['user_id' => $user_id]);
$wpdb->insert($token_table, [
'user_id' => $user_id,
'openid' => $openid,
'unionid' => $unionid,
'session_key' => $session_key,
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'sign_secret' => $sign_secret,
'access_expire' => $access_expire,
'refresh_expire' => $refresh_expire,
'created_at' => time(),
'updated_at' => time()
], ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d']);
return [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'sign_secret' => $sign_secret,
'expires_in' => 7200,
'user_id' => $user_id
];
}
// ====================== 4. REST API 接口 ======================
add_action('rest_api_init', function() {
// 1. 登录接口(获取Token)
register_rest_route('weapp/v1', '/login', [
'methods' => 'POST',
'callback' => 'weapp_login_api',
'permission_callback' => '__return_true'
]);
// 2. 刷新Token接口
register_rest_route('weapp/v1', '/token/refresh', [
'methods' => 'POST',
'callback' => 'weapp_refresh_token_api',
'permission_callback' => '__return_true'
]);
// 3. 用户信息接口(需鉴权+签名)
register_rest_route('weapp/v1', '/user/info', [
'methods' => 'GET',
'callback' => 'weapp_user_info_api',
'permission_callback' => '__return_true'
]);
});
/**
* 登录接口实现
*/
function weapp_login_api($request) {
$log_data = [
'api_path' => '/weapp/v1/login',
'request_method' => $request->get_method(),
'request_params' => $request->get_params(),
'token_status' => 'none'
];
try {
$code = $request->get_param('code');
if (!$code) {
$log_data['response_code'] = 400;
$log_data['response_msg'] = '缺少code';
weapp_log_request($log_data);
return new WP_Error('missing_code', '缺少登录code', ['status' => 400]);
}
// 调用微信接口
$wx_data = weapp_code2session($code);
if (!$wx_data) {
$log_data['response_code'] = 500;
$log_data['response_msg'] = '微信接口调用失败';
weapp_log_request($log_data);
return new WP_Error('wx_api_error', '登录失败', ['status' => 500]);
}
// 绑定用户并生成Token
$token_data = weapp_bind_user_and_token($wx_data['openid'], $wx_data['session_key'], $wx_data['unionid'] ?? '');
if (!$token_data) {
$log_data['response_code'] = 500;
$log_data['response_msg'] = 'Token生成失败';
weapp_log_request($log_data);
return new WP_Error('token_error', '登录失败', ['status' => 500]);
}
// 记录日志
$log_data['response_code'] = 0;
$log_data['response_msg'] = '登录成功';
$log_data['user_id'] = $token_data['user_id'];
$log_data['openid'] = $wx_data['openid'];
weapp_log_request($log_data);
return [
'code' => 0,
'msg' => '登录成功',
'data' => $token_data
];
} catch (Exception $e) {
$log_data['response_code'] = 500;
$log_data['response_msg'] = '服务器异常:' . $e->getMessage();
weapp_log_request($log_data);
return new WP_Error('server_error', '服务器异常', ['status' => 500]);
}
}
/**
* 刷新Token接口实现
*/
function weapp_refresh_token_api($request) {
global $wpdb;
$log_data = [
'api_path' => '/weapp/v1/token/refresh',
'request_method' => $request->get_method(),
'request_params' => $request->get_params()
];
try {
$refresh_token = $request->get_param('refresh_token');
if (!$refresh_token) {
$log_data['response_code'] = 400;
$log_data['response_msg'] = '缺少refresh_token';
weapp_log_request($log_data);
return new WP_Error('missing_refresh_token', '缺少刷新Token', ['status' => 400]);
}
// 验证刷新Token
$token_table = $wpdb->prefix . 'weapp_token';
$token_info = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$token_table} WHERE refresh_token = %s", $refresh_token), ARRAY_A);
if (!$token_info || $token_info['refresh_expire'] < time()) {
$log_data['response_code'] = 401;
$log_data['response_msg'] = '刷新Token过期';
$log_data['token_status'] = 'expired';
weapp_log_request($log_data);
return new WP_Error('expired_refresh_token', '请重新登录', ['status' => 401]);
}
// 生成新的访问Token
$new_access_token = weapp_generate_token(32);
$new_sign_secret = weapp_generate_token(16);
$new_access_expire = time() + 7200;
$wpdb->update($token_table, [
'access_token' => $new_access_token,
'sign_secret' => $new_sign_secret,
'access_expire' => $new_access_expire,
'updated_at' => time()
], ['refresh_token' => $refresh_token], ['%s', '%s', '%d', '%d'], ['%s']);
// 记录日志
$log_data['response_code'] = 0;
$log_data['response_msg'] = '刷新成功';
$log_data['user_id'] = $token_info['user_id'];
$log_data['openid'] = $token_info['openid'];
$log_data['token_status'] = 'valid';
weapp_log_request($log_data);
return [
'code' => 0,
'msg' => '刷新成功',
'data' => [
'access_token' => $new_access_token,
'sign_secret' => $new_sign_secret,
'expires_in' => 7200
]
];
} catch (Exception $e) {
$log_data['response_code'] = 500;
$log_data['response_msg'] = '服务器异常:' . $e->getMessage();
weapp_log_request($log_data);
return new WP_Error('server_error', '服务器异常', ['status' => 500]);
}
}
/**
* 用户信息接口实现
*/
function weapp_user_info_api($request) {
global $wpdb;
$log_data = [
'api_path' => '/weapp/v1/user/info',
'request_method' => $request->get_method(),
'request_params' => $request->get_params()
];
try {
// 验证Token
$auth_header = $request->get_header('Authorization');
if (!$auth_header || !str_starts_with($auth_header, 'Bearer ')) {
$log_data['response_code'] = 401;
$log_data['response_msg'] = 'Token格式错误';
$log_data['token_status'] = 'invalid';
weapp_log_request($log_data);
return new WP_Error('invalid_token', 'Token错误', ['status' => 401]);
}
$access_token = substr($auth_header, 7);
$token_table = $wpdb->prefix . 'weapp_token';
$token_info = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$token_table} WHERE access_token = %s", $access_token), ARRAY_A);
if (!$token_info || $token_info['access_expire'] < time()) {
$log_data['response_code'] = 401;
$log_data['response_msg'] = $token_info ? 'Token过期' : 'Token不存在';
$log_data['token_status'] = $token_info ? 'expired' : 'invalid';
weapp_log_request($log_data);
return new WP_Error('token_error', $log_data['response_msg'], ['status' => 401]);
}
// 验证签名
$request_params = $request->get_params();
if (!weapp_verify_sign($request_params, $access_token, $token_info['sign_secret'])) {
$log_data['response_code'] = 403;
$log_data['response_msg'] = '签名验证失败';
$log_data['user_id'] = $token_info['user_id'];
$log_data['openid'] = $token_info['openid'];
$log_data['token_status'] = 'valid';
weapp_log_request($log_data);
return new WP_Error('sign_error', '请求非法', ['status' => 403]);
}
// 返回用户信息
$user = get_user_by('id', $token_info['user_id']);
$log_data['response_code'] = 0;
$log_data['response_msg'] = 'success';
$log_data['user_id'] = $token_info['user_id'];
$log_data['openid'] = $token_info['openid'];
$log_data['token_status'] = 'valid';
weapp_log_request($log_data);
return [
'code' => 0,
'msg' => 'success',
'data' => [
'user_id' => $user->ID,
'nickname' => $user->display_name,
'openid' => $token_info['openid'],
'role' => $user->roles[0]
]
];
} catch (Exception $e) {
$log_data['response_code'] = 500;
$log_data['response_msg'] = '服务器异常:' . $e->getMessage();
weapp_log_request($log_data);
return new WP_Error('server_error', '服务器异常', ['status' => 500]);
}
}
插件卸载文件:uninstall.php(可选)
<?php
// 确保从WordPress卸载
if (!defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
global $wpdb;
// 删除数据库表
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}weapp_token");
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}weapp_request_log");
// 清除定时任务
wp_clear_scheduled_hook('weapp_clean_old_logs');
微信小程序前端工具类
请求封装:request.js
/**
* 微信小程序请求工具类(含Token管理、签名生成)
*/
const config = {
baseUrl: 'https://你的WP域名/wp-json/weapp/v1',
timeout: 10000,
retryCount: 1,
tokenKey: {
access: 'access_token',
refresh: 'refresh_token',
expire: 'token_expire',
signSecret: 'sign_secret'
}
};
// 引入MD5/HMAC-SHA256库(需自行引入crypto-js或md5.js)
import md5 from './md5.js';
import CryptoJS from './crypto-js.js';
// 刷新锁和Promise
let refreshLock = false;
let refreshPromise = null;
// Token操作
const getToken = () => ({
accessToken: uni.getStorageSync(config.tokenKey.access) || '',
refreshToken: uni.getStorageSync(config.tokenKey.refresh) || '',
expireTime: uni.getStorageSync(config.tokenKey.expire) || 0,
signSecret: uni.getStorageSync(config.tokenKey.signSecret) || ''
});
const setToken = (tokenData) => {
if (tokenData.access_token) uni.setStorageSync(config.tokenKey.access, tokenData.access_token);
if (tokenData.refresh_token) uni.setStorageSync(config.tokenKey.refresh, tokenData.refresh_token);
if (tokenData.sign_secret) uni.setStorageSync(config.tokenKey.signSecret, tokenData.sign_secret);
if (tokenData.expires_in) uni.setStorageSync(config.tokenKey.expire, Date.now() + tokenData.expires_in * 1000);
};
const clearToken = () => {
uni.removeStorageSync(config.tokenKey.access);
uni.removeStorageSync(config.tokenKey.refresh);
uni.removeStorageSync(config.tokenKey.expire);
uni.removeStorageSync(config.tokenKey.signSecret);
};
// 生成签名
const generateSign = (data, accessToken) => {
const { signSecret } = getToken();
const timestamp = Math.floor(Date.now() / 1000);
const nonce = Math.random().toString(36).substr(2, 10);
// 过滤空值+统一类型
const cleanData = {};
Object.keys(data).forEach(key => {
if (data[key] !== undefined && data[key] !== null && data[key] !== '') {
cleanData[key] = String(data[key]);
}
});
// 拼接签名参数
const signObj = {
...cleanData,
timestamp: String(timestamp),
nonce: String(nonce),
secret: signSecret,
token: accessToken
};
// 升序排序
const sortedKeys = Object.keys(signObj).sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'ascii' }));
let signStr = '';
sortedKeys.forEach((key, index) => {
signStr += `${key}=${signObj[key]}${index < sortedKeys.length - 1 ? '&' : ''}`;
});
// HMAC-SHA256签名
const sign = CryptoJS.HmacSHA256(signStr, '你的固定密钥').toString(CryptoJS.enc.Hex);
return { ...cleanData, timestamp, nonce, sign };
};
// 刷新Token
const refreshToken = async () => {
if (refreshPromise) return refreshPromise;
refreshLock = true;
refreshPromise = new Promise(async (resolve) => {
const { refreshToken } = getToken();
if (!refreshToken) {
clearToken();
uni.navigateTo({ url: '/pages/login/login' });
refreshLock = false;
refreshPromise = null;
resolve(false);
return;
}
try {
const { data } = await uni.request({
url: `${config.baseUrl}/token/refresh`,
method: 'POST',
data: { refresh_token: refreshToken },
timeout: config.timeout
});
if (data.code === 0) {
setToken(data.data);
refreshLock = false;
refreshPromise = null;
resolve(true);
} else {
clearToken();
uni.navigateTo({ url: '/pages/login/login' });
refreshLock = false;
refreshPromise = null;
resolve(false);
}
} catch (err) {
clearToken();
uni.navigateTo({ url: '/pages/login/login' });
refreshLock = false;
refreshPromise = null;
resolve(false);
}
});
return refreshPromise;
};
// 核心请求方法
const request = async (options, retry = 0) => {
const { url, method = 'GET', data = {}, header = {}, needAuth = true } = options;
const fullUrl = url.startsWith('http') ? url : `${config.baseUrl}${url}`;
const requestHeader = { 'content-type': 'application/json', ...header };
// 鉴权处理
if (needAuth) {
const { accessToken, expireTime } = getToken();
if (!accessToken) {
uni.navigateTo({ url: '/pages/login/login' });
return Promise.reject(new Error('未登录'));
}
// Token过期先刷新
if (Date.now() > expireTime && retry < config.retryCount) {
const refreshSuccess = await refreshToken();
if (!refreshSuccess) return Promise.reject(new Error('Token刷新失败'));
return request(options, retry + 1);
}
// 添加Token和签名
requestHeader.Authorization = `Bearer ${accessToken}`;
data = generateSign(data, accessToken);
}
try {
const { data: resData, statusCode } = await uni.request({
url: fullUrl,
method,
data,
header: requestHeader,
timeout: config.timeout
});
if (statusCode === 200) {
if (resData.code === 0) return resData.data;
uni.showToast({ title: resData.msg || '请求失败', icon: 'none' });
return Promise.reject(new Error(resData.msg));
} else {
uni.showToast({ title: `请求错误:${statusCode}`, icon: 'none' });
return Promise.reject(new Error(`HTTP错误:${statusCode}`));
}
} catch (err) {
uni.showToast({ title: '网络异常', icon: 'none' });
return Promise.reject(err);
}
};
// 简化请求方法
export default {
get: (url, options = {}) => request({ url, method: 'GET', ...options }),
post: (url, data, options = {}) => request({ url, method: 'POST', data, ...options }),
login: (code) => request({ url: '/login', method: 'POST', data: { code }, needAuth: false }),
logout: () => clearToken()
};
后端部署说明
- 将
wp-weapp-token 文件夹放入 wp-content/plugins/ 目录;
- 登录 WordPress 后台 → 「插件」→ 激活「WordPress 微信小程序 Token 管理」;
- 在
wp-config.php 中添加以下配置(替换为实际值):
define('WEAPP_SIGN_SECRET', '32位随机签名密钥');
define('WEAPP_APPID', '你的小程序AppID');
define('WEAPP_SECRET', '你的小程序AppSecret');
前端部署说明
- 将
request.js 放入小程序项目 utils 目录;
- 引入 MD5 / CryptoJS 库(可从开源仓库下载);
- 修改
request.js 中的 baseUrl 为你的 WordPress 域名;
- 示例调用方式如下:
import http from '@/utils/request.js';
// 登录
async function login() {
const { code } = await uni.login();
const tokenData = await http.login(code);
uni.showToast({ title: '登录成功' });
}
// 获取用户信息
async function getUserInfo() {
const userInfo = await http.get('/user/info');
console.log(userInfo);
}