Vue3 + ThinkPHP6 JWT 教程

Vue3 + ThinkPHP6 JWT 完整教程(前后端分离登录鉴权)

整体流程

  1. 前端提交账号密码 → TP6后端校验账号密码
  2. 校验成功,后端生成JWT令牌返回前端
  3. 前端保存token,axios请求拦截器自动在请求头带上Authorization: Bearer token
  4. 后端JWT中间件校验token,合法放行接口;失效/错误返回401
  5. 前端响应拦截捕获401,清除token、跳转到登录页;路由守卫阻止未登录访问需要权限的页面

两种方案:① 原生firebase/jwt手写JWT(理解原理);② thans/tp-jwt-auth封装库(项目推荐),下面同时给出。


一、ThinkPHP6 后端部分

1. 安装依赖

方案A:官方推荐扩展 thans/tp-jwt-auth(推荐项目使用)

bash 复制代码
composer require thans/tp-jwt-auth

复制配置文件:

复制代码
php think jwt:publish

修改 .env 配置 jwt 密钥、过期时间

ini 复制代码
JWT_SECRET=你的超级密钥,尽量长,不要泄露
JWT_TTL=7200       # token有效期 单位秒 2小时
JWT_REFRESH_TTL=20160 #刷新有效期 14天

方案B:原生 firebase/jwt(学习原理)

bash 复制代码
composer require firebase/php-jwt

2. 登录控制器(生成Token)

路由:POST /api/login

php 复制代码
<?php
namespace app\controller;
use think\Request;
use thans\jwt\facade\JWTAuth;

class User
{
    //登录接口
    public function login(Request $request)
    {
        $username = $request->post('username');
        $password = $request->post('password');
        // 查询用户(自行替换user表,密码建议password_hash哈希存储)
        $user = \app\model\User::where('username',$username)->find();
        if(!$user || !password_verify($password,$user['password'])){
            return json(['code'=>400,'msg'=>'账号或密码错误']);
        }
        //生成token,payload写入uid等信息
        $token = JWTAuth::builder(['uid'=>$user['id'],'username'=>$user['username']]);
        return json([
            'code'=>200,
            'msg'=>'登录成功',
            'data'=>[
                'token'=>$token
            ]
        ]);
    }
}

3. 创建JWT鉴权中间件(thans扩展自带中间件)

app/middleware.php 注册中间件

kotlin 复制代码
return [
    \thans\jwt\middleware\JWTAuth::class
];

路由定义 route/api.php

php 复制代码
<?php
use think\facade\Route;
//登录不需要鉴权
Route::post('login','User/login');
//需要token鉴权的接口,加jwt中间件
Route::group(function(){
    Route::get('info','User/info'); //获取用户信息接口
})->middleware(\thans\jwt\middleware\JWTAuth::class);

原生 firebase/jwt 手写中间件(了解原理)

app/middleware/CheckJwt.php

php 复制代码
<?php
namespace app\middleware;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\Request;
class CheckJwt
{
    public function handle(Request $request, \Closure $next)
    {
        $header = $request->header('authorization');
        if(!$header){
            return json(['code'=>401,'msg'=>'缺少token'],401);
        }
        $token = str_replace('Bearer ','',$header);
        $key = '你的密钥';
        try{
            $decoded = JWT::decode($token,new Key($key,'HS256'));
            //把解析出来的uid挂载到request
            $request->uid = $decoded->uid;
        }catch(\Exception $e){
            return json(['code'=>401,'msg'=>'token无效或已过期'],401);
        }
        return $next($request);
    }
}

4. 获取用户信息接口(需要鉴权)

php 复制代码
public function info()
{
    $payload = JWTAuth::auth();
    $uid = $payload['uid'];
    $user = \app\model\User::find($uid);
    return json(['code'=>200,'data'=>$user]);
}

跨域处理:TP6建议安装 composer require topthink/think-cors 解决前端axios跨域


二、Vue3前端部分(Vite + axios + Pinia + VueRouter)

1. 安装包

复制代码
npm install axios pinia vue-router

2. axios封装 src/utils/request.js

javascript 复制代码
import axios from 'axios'
const service = axios.create({
  baseURL: 'http://tp6-api域名/api',
  timeout: 10000
})

// 请求拦截器:每次请求自动带上token
service.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if(token){
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
},err=>Promise.reject(err))

//响应拦截器:捕获401,token失效
service.interceptors.response.use(res=>{
  return res.data
},err=>{
  if(err.response && err.response.status ===401){
    localStorage.removeItem('token')
    //跳转到登录页
    import('@/router').then(({default:router})=>{
      router.push('/login')
    })
  }
  return Promise.reject(err)
})
export default service

3. API封装 src/api/user.js

javascript 复制代码
import request from '@/utils/request'
//登录
export function login(data){
  return request.post('/login',data)
}
//获取用户信息
export function getUserInfo(){
  return request.get('/info')
}

4. Pinia 状态管理 src/stores/user.js(保存登录状态)

javascript 复制代码
import {defineStore} from 'pinia'
export const useUserStore = defineStore('user',{
  state:()=>({
    token:localStorage.getItem('token') || '',
    userInfo:{}
  }),
  actions:{
    setToken(val){
      this.token = val
      localStorage.setItem('token',val)
    },
    logout(){
      this.token=''
      this.userInfo={}
      localStorage.removeItem('token')
    }
  }
})

5. 路由守卫 src/router/index.js

javascript 复制代码
import {createRouter,createWebHistory} from 'vue-router'
import {useUserStore} from '@/stores/user'
const routes = [
  {path:'/login',component:()=>import('@/views/login.vue')},
  {path:'/',component:()=>import('@/views/home.vue'),meta:{requireAuth:true}}
]
const router = createRouter({
  history:createWebHistory(),
  routes
})
//全局前置守卫
router.beforeEach((to,from,next)=>{
  const userStore = useUserStore()
  if(to.meta.requireAuth && !userStore.token){
    next('/login')
  }else{
    next()
  }
})
export default router

6. 登录页面 views/login.vue

xml 复制代码
<template>
  <div>
    <input v-model="form.username" placeholder="账号"/>
    <input v-model="form.password" type="password" placeholder="密码"/>
    <button @click="handleLogin">登录</button>
  </div>
</template>
<script setup>
import {reactive} from 'vue'
import {useRouter} from 'vue-router'
import {login} from '@/api/user'
import {useUserStore} from '@/stores/user'
const router = useRouter()
const userStore = useUserStore()
const form = reactive({username:'',password:''})

const handleLogin = async ()=>{
  const res = await login(form)
  if(res.code === 200){
    userStore.setToken(res.data.token)
    router.push('/')
  }
}
</script>

三、常见坑

  1. Authorization 大小写 :后端读取Authorization,不要写成小写authorization,nginx有时候会丢header,需要配置。
  2. Bearer后面有空格 :Bearer xxxx 空格不能丢,后端解析会失败。
  3. 跨域:OPTIONS预检请求,TP6必须配置cors,否则header带不上token。
  4. JWT密钥:生产环境密钥不要硬写代码,放到.env,不要提交到git。
  5. 退出登录:JWT本身无状态,单纯前端清token;如果需要强制失效,要用Redis黑名单。
  6. token刷新:thans/jwt-auth自带refreshToken,适合长登录场景。

安全建议

  • 密码入库必须password_hash加密,禁止明文
  • 生产环境接口使用HTTPS
  • payload不要存放敏感数据(JWT可以前端解码)
  • 重要接口增加Redis黑名单实现token主动失效
相关推荐
金銀銅鐵9 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路10 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x10 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn12 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈13 小时前
AI 读你的提示词,其实很没耐心
后端
用户83562907805113 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我1234513 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒13 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记14 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk5987814 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端