thinkphp6:前后端分离使用表单令牌(php 8.1.1 / thinkphp v6.0.10LTS/vue 3.2.26)
一,vue前端代码
1,Login.vue<template> <div style="padding:20px;display: flex;align-items:center;justify-content: center;"> <form :model="account" style="margin-top:50px;width:400px;"> <input v-model="account.username" placeholder="请输入用户名" style="width:392px;font-size:16px;" /><br/> <input v-model="account.password" type="password" placeholder="请输入密码" style="width:392px;margin-top:10px;font-size:16px;" /><br/> <div @click="login" style="margin-top:10px;width: 100%;height:40px;line-height:40px;background: #ff0000;border-radius: 10px;"> 登录 div> <div @click="info" style="margin-top:10px;width: 100%;height:40px;line-height:40px;background: #ff0000;border-radius: 10px;"> info div> <div class="text-align-right"> div> form> div> template> <script> import { ref, reactive } from "vue"; import { ElMessage } from "element-plus"; import { apiLogin,apiInfo,apiToken} from '@/api/api'; export default { name: "Login", setup() { const accountRef = ref(null); //表单字段 const account = reactive({ username: "", password: "", }); const formToken = ref(""); const getToken = () => { apiToken().then(res => { //成功 if (res.code == 0) { formToken.value = res.data.token; } else { ElMessage.error("获取表单令牌失败:"+res.msg); } }).catch((error) => { console.log(error) }) } getToken(); //登录 const login = async () => { console.log('begin login'); var data = new FormData(); data.append("username",account.username); data.append("password",account.password); data.append("X-CSRF-TOKEN",formToken.value); apiLogin(data).then(res => { //成功 if (res.code == 0) { //保存jwt token到本地 localStorage.setItem('token', res.data.token); //提示 ElMessage.success("登录成功!"); } else { ElMessage.error("登录失败:"+res.msg); } }).catch((error) => { console.log(error) }) }; const login2 = () => { console.log('begin login2'); } const info = () => { apiInfo().then(res => { //成功 if (res.code == 0) { console.log(res.data); } else { ElMessage.error("用户信息获取失败:"+res.msg); } }).catch((error) => { console.log(error) }) } return { account, accountRef, login, login2, info, }; }, } script> <style scoped> style>2,api/axios.js
... //post export function postForm (url, params) { return new Promise((resolve, reject) => { let headers = { 'Content-Type': 'application/x-www-form-urlencoded', } //判断params中是否包含X-CSRF-TOKEN var token = ""; if (params.has("X-CSRF-TOKEN")) { token = params.get("X-CSRF-TOKEN"); params.delete("X-CSRF-TOKEN"); headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': token, } } _axios.post(url, params,{headers}) .then(res => { resolve(res.data) }) .catch(err => { console.log("api error:"); console.log(err); //alert(err); reject(err.data) }) }) } ...
3,api/api.js
//get, import { get, postForm} from './axios' //user export const apiLogin = pLogin => postForm('/api/auth/login', pLogin) ...
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest
对应的源码可以访问这里获取: https://github.com/liuhongdi/
或: https://gitee.com/liuhongdi
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,服务端代码:
1,controller/Auth.php<?php declare (strict_types = 1); namespace app\controller; use app\BaseController; use think\facade\Cache; use think\Request; use app\result\Result; use think\response\Json; use app\validate\Login as LoginValidate; use app\validate\GoodsList as GoodsListValidate; use think\exception\ValidateException; use app\lib\util\JwtUtil; class Auth extends BaseController { /* *得到form token * *@return \think\Response * */ public function token():Json { $type="sha1"; $type = is_callable($type) ? $type : 'md5'; $token = call_user_func($type, $this->request->server('REQUEST_TIME_FLOAT')); $key = uniqid(); Cache::set($key,$token,300); $res = [ "token"=>$key.$token, ]; return Result::Success($res); } /** * 登录 * * @return \think\Response */ public function login():Json { // Header验证 if ($this->request->header('X-CSRF-TOKEN')){ $reqToken = $this->request->header('X-CSRF-TOKEN'); $key = substr($reqToken,0,13); $value = Cache::get($key); $origToken = $key.$value; if ($origToken === $reqToken) { //验证通过 } else { //验证不通过 return Result::Error(422,'表单token错误'); } } else { return Result::Error(423,'找不到表单token'); } try { validate(LoginValidate::class) ->check($_POST); } catch (ValidateException $e) { // 验证失败 输出错误信息 return Result::Error(422,$e->getError()); } if ($_POST["username"] == "dddddd" && $_POST["password"] == "111111"){ //验证成功,生成jwt返回 $userId = 123; $jUtil = new JwtUtil(); $token = $jUtil->createJwt($userId); $res = ["token"=>$token]; // 防止重复提交 Cache::delete($key); return Result::Success($res); } else { return Result::Error(422,"用户名密码错误"); } } }2,Result.php
<?php namespace app\result; use think\response\Json; class Result { //success static public function Success($data):Json { $rs = [ 'code'=>0, 'msg'=>"success", 'data'=>$data, ]; return json($rs); } //error static public function Error($code,$msg):Json { $rs = [ 'code'=>$code, 'msg'=>$msg, 'data'=>"", ]; return json($rs); } }
三,测试效果:
1,界面 2, 提交与返回:四,查看vue的版本
liuhongdi@lhdpc:/data/vue/demo1$ npm list vue demo1@0.1.0 /data/vue/demo1 ├─┬ @vue/cli-plugin-babel@4.5.15 │ └─┬ @vue/babel-preset-app@4.5.15 │ └── vue@3.2.26 deduped ├─┬ element-plus@1.2.0-beta.6 │ ├─┬ @element-plus/icons-vue@0.2.4 │ │ └── vue@3.2.26 deduped │ ├─┬ @vueuse/core@7.4.1 │ │ ├─┬ @vueuse/shared@7.4.1 │ │ │ └── vue@3.2.26 deduped │ │ ├─┬ vue-demi@0.12.1 │ │ │ └── vue@3.2.26 deduped │ │ └── vue@3.2.26 deduped │ └── vue@3.2.26 deduped └─┬ vue@3.2.26 └─┬ @vue/server-renderer@3.2.26 └── vue@3.2.26 deduped
五,查看php和thinkphp的版本:
php:liuhongdi@lhdpc:/data/php/admapi$ php --version PHP 8.1.1 (cli) (built: Dec 20 2021 16:12:16) (NTS) Copyright (c) The PHP Group Zend Engine v4.1.1, Copyright (c) Zend Technologies with Zend OPcache v8.1.1, Copyright (c), by Zend Technologiesthinkphp:
liuhongdi@lhdpc:/var/www/html$ cd /data/php/admapi/ liuhongdi@lhdpc:/data/php/admapi$ php think version v6.0.10LTS