thinkphp8结合jwt与微信小程序接口鉴权

环境:debian13\php8.3\tp8; jwt版本:7.1;mySQL:8.4/9.7

bash 复制代码
composer require firebase/php-jwt
sql 复制代码
CREATE TABLE `user` (
    `id` int unsigned NOT NULL AUTO_INCREMENT,
    `openid` varchar(64) NOT NULL DEFAULT '',
    `nickname` varchar(100) NOT NULL DEFAULT '',
    `avatar` varchar(255) NOT NULL DEFAULT '',
    `created_at` datetime DEFAULT NULL,
    `updated_at` datetime DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
php 复制代码
#app/api/route/app.php

<?php

declare(strict_types=1);

use think\facade\Route;

Route::post('login', 'Auth/login');
Route::get('user', 'Auth/user');
php 复制代码
# app/api/config/jwt.php

<?php

declare(strict_types=1);

return [
    'key'       => env('JWT.KEY', ''),
    'algorithm' => 'HS256',
    'expire'    => 7200,
    'issuer'    => 'wechat-mini-program',
];

# app/api/config/wechat.php
<?php

declare(strict_types=1);

return [
    'app_id'     => env('WECHAT.APP_ID', ''),
    'app_secret' => env('WECHAT.APP_SECRET', ''),
];
php 复制代码
# app/api/controller/Auth.php

<?php

declare(strict_types=1);

namespace app\api\controller;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\facade\Db;
use think\facade\Http;
use think\Request;
use think\response\Json;
use Throwable;

class Auth
{
    /**
     * 微信小程序登录
     */
    public function login(Request $request): Json
    {
        $code = trim((string) $request->post('code', ''));

        if ($code === '') {
            return json([
                'code' => 400,
                'msg'  => '缺少微信登录 code',
                'data' => null,
            ], 400);
        }

        try {
            // 使用 code 换取 openid 和 session_key
            $response = Http::get(
                'https://api.weixin.qq.com/sns/jscode2session',
                [
                    'appid'      => config('wechat.app_id'),
                    'secret'     => config('wechat.app_secret'),
                    'js_code'    => $code,
                    'grant_type' => 'authorization_code',
                ]
            );

            $wechatData = $response->json();

            if (empty($wechatData['openid'])) {
                return json([
                    'code'  => 400,
                    'msg'   => '微信登录失败',
                    'data'  => null,
                    'error' => $wechatData,
                ], 400);
            }

            $openid = $wechatData['openid'];

            // 查询本地用户
            $user = Db::name('user')
                ->where('openid', $openid)
                ->find();

            // 第一次登录,创建本地用户
            if (!$user) {
                $userId = Db::name('user')->insertGetId([
                    'openid'     => $openid,
                    'created_at' => date('Y-m-d H:i:s'),
                    'updated_at' => date('Y-m-d H:i:s'),
                ]);

                $user = [
                    'id'     => $userId,
                    'openid' => $openid,
                ];
            }

            $now = time();

            $payload = [
                'iss' => config('jwt.issuer'),
                'iat' => $now,
                'nbf' => $now,
                'exp' => $now + (int) config('jwt.expire'),

                // JWT 中保存本地用户 ID
                'sub' => (string) $user['id'],

                'user' => [
                    'id' => $user['id'],
                ],
            ];

            $token = JWT::encode(
                $payload,
                config('jwt.key'),
                config('jwt.algorithm')
            );

            return json([
                'code' => 0,
                'msg'  => '登录成功',
                'data' => [
                    'token'      => $token,
                    'token_type' => 'Bearer',
                    'expires_in' => config('jwt.expire'),
                    'user_id'    => $user['id'],
                ],
            ]);
        } catch (Throwable $exception) {
            return json([
                'code'  => 500,
                'msg'   => '服务器处理失败',
                'data'  => null,
                'error' => $exception->getMessage(),
            ], 500);
        }
    }

    /**
     * 验证 JWT
     */
    public function user(Request $request): Json
    {
        $authorization = $request->header('authorization', '');

        if (!preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches)) {
            return json([
                'code' => 401,
                'msg'  => '请传入 Bearer Token',
                'data' => null,
            ], 401);
        }

        try {
            $token = trim($matches[1]);

            $decoded = JWT::decode(
                $token,
                new Key(
                    config('jwt.key'),
                    config('jwt.algorithm')
                )
            );

            $userId = (int) $decoded->sub;

            $user = Db::name('user')
                ->where('id', $userId)
                ->field('id,nickname,avatar')
                ->find();

            if (!$user) {
                return json([
                    'code' => 404,
                    'msg'  => '用户不存在',
                    'data' => null,
                ], 404);
            }

            return json([
                'code' => 0,
                'msg'  => '获取成功',
                'data' => $user,
            ]);
        } catch (Throwable $exception) {
            return json([
                'code'  => 401,
                'msg'   => 'Token 无效或已经过期',
                'data'  => null,
                'error' => $exception->getMessage(),
            ], 401);
        }
    }
}
javascript 复制代码
// utils/request.js

const BASE_URL = 'https://你的域名/api';

export function request({
    url,
    method = 'GET',
    data = {},
    auth = true
}) {
    const token = wx.getStorageSync('token');

    return new Promise((resolve, reject) => {
        wx.request({
            url: `${BASE_URL}${url}`,
            method,
            data,
            header: {
                'content-type': 'application/json',
                ...(auth && token
                    ? { Authorization: `Bearer ${token}` }
                    : {})
            },
            success: ({ statusCode, data: responseData }) => {
                if (statusCode === 401) {
                    wx.removeStorageSync('token');

                    wx.showToast({
                        title: '登录已过期,请重新登录',
                        icon: 'none'
                    });

                    reject(responseData);
                    return;
                }

                if (statusCode < 200 || statusCode >= 300) {
                    reject(responseData);
                    return;
                }

                resolve(responseData);
            },
            fail: reject
        });
    });
}
javascript 复制代码
// 调用登录接口:
import { request } from '../../utils/request';

wx.login({
    success: async ({ code }) => {
        const result = await request({
            url: '/login',
            method: 'POST',
            data: { code },
            auth: false
        });

        wx.setStorageSync('token', result.data.token);
    }
});

// 调用需要登录的接口:
import { request } from '../../utils/request';

Page({
    async onLoad() {
        try {
            const result = await request({
                url: '/user'
            });

            console.log(result.data);
        } catch (error) {
            console.error(error);
        }
    }
});
相关推荐
Geek_Vison8 分钟前
小程序容器如何抹平系统差异,让一个小程序运行在 iOS、安卓、鸿蒙和电脑端
小程序·uni-app·harmonyos·mpaas
微笑的曙光2 小时前
第一期 · 本地优先 MVP:不写一个后端,如何做出完整体验
微信小程序
EatFan3 小时前
Java接入微信支付保姆式教程(三):SpringBoot 接入微信支付并完成统一下单
微信小程序·微信支付·springboot
2601_949950634 小时前
练题簿:把备考资料装进小程序,随时开启高效在线刷题
人工智能·小程序·刷题·练习·小程序推荐
QQ_21696290965 小时前
基于SpringBoot+Vue的小生活平台的设计与实现
java·数据库·vue.js·spring boot·spring·微信小程序·生活
飞梦工作室5 小时前
H5页面能否直接播放视频号直播?实战方案与踩坑总结
微信小程序·小程序
河南花仙子科技20 小时前
企业定制小游戏助力品牌软性传播
大数据·科技·游戏·小程序
StevenLdh21 小时前
情绪小恐龙:一个微信小程序从架构设计到部署上线的全记录
微信小程序·小程序·notepad++
m0_5873830021 小时前
折扣卡CPS软件开发实战:从系统架构设计到上线指南
java·小程序·架构·需求分析
mykj155121 小时前
赛事报名小程序系统:一站式解决赛事管理难题
小程序·app开发·体育赛事报名小程序·赛事app