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主动失效
相关推荐
jsl_jsl_jsl1 小时前
MongoDB速记
后端
Gopher_HBo1 小时前
beego 配置模块(core/config)
后端
freewcx1 小时前
StringRedisTemplate
后端
小园子的小菜1 小时前
LangChain核心进阶解析:输出解析、聊天记忆与回调机制实战
后端
freewcx1 小时前
SpringDataRedis的序列化方式
后端
YHL1 小时前
🚀 NestJS 后端开发实战:从设计模式到 CRUD 全栈
后端
晚安日记wanna1 小时前
接口幂等性怎么保证
后端·面试
风中的小熊生气1 小时前
Spring Boot 面试知识(一):从启动流程到 AOP 与事务失效
spring boot·后端·面试
神秘的猪头1 小时前
Gin 项目为什么要分 Handler、Service、Repository?顺便讲透接口、依赖注入与指针
后端·go