uni-app 小程式 Laravel+jwt 權限認證系列
uni-app開發教學欄位介紹系列權限認證的方法。
推薦:funi-app開發教學
#環境說明
uni-app
laravel 5.7
jwt- auth 1.0.0
權限認證整體說明
- #設計表格結構 ##前端request 類別
- #有關權限認證的js 封裝包含無感知刷新token
- laravel auth 中間件包含無感知刷新token
- #取得手機號碼登陸
- 無痛刷新access_token 思路
- 小程式如何判斷登陸狀態
設計表結構
和一般設計表沒有什麼區別,如果是多平台小程序,透過account_id 關聯聯合表。CREATE TABLE `users` ( `u_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '账号id', `u_username` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号隐藏 ', `u_nickname` varchar(15) NOT NULL COMMENT '分配用户名', `u_headimg` varchar(200) DEFAULT NULL COMMENT '头像', `u_province` varchar(50) DEFAULT NULL, `u_city` varchar(50) DEFAULT NULL, `u_platform` varchar(30) NOT NULL COMMENT '平台:小程序wx,bd等', `u_mobile` char(11) NOT NULL COMMENT '手机号必须授权', `u_openid` varchar(100) DEFAULT NULL COMMENT 'openid', `u_regtime` timestamp NULL DEFAULT NULL COMMENT '注册时间', `u_login_time` timestamp NULL DEFAULT NULL COMMENT '最后登陆时间', `u_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '0禁用1正常', `account_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '平台联合id', PRIMARY KEY (`u_id`), KEY `platform` (`u_platform`,`u_mobile`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;登入後複製
2. 前端request 類別
一個較不錯的request 類別luch-request ,支援動態修改配置、攔截器,在uni-app 插件市場可以找到。
其中 request.js 不需要更改。自訂邏輯在 index.js。
index.js
import Request from './request';import jwt from '@/utils/auth/jwt.js'; // jwt 管理 见下文const http = new Request();const baseUrl = 'http://xxx'; // api 地址var platform = ''; // 登陆时需知道来自哪个平台的小程序用户// #ifdef MP-BAIDUplatform = 'MP-BAIDU';// #endif/* 设置全局配置 */http.setConfig((config) => { config.baseUrl = baseUrl; //设置 api 地址 config.header = { ...config.header } return config})/* 请求之前拦截器 */http.interceptor.request((config, cancel) => { if (!platform) {cancel('缺少平台参数');} config.header = { ...config.header, platform:platform } if (config.custom.auth) { // 需要权限认证的路由 需携带自定义参数 {custom: {auth: true}} config.header.Authorization = jwt.getAccessToken(); } return config})http.interceptor.response(async (response) => { /* 请求之后拦截器 */ console.log(response); // 如果是需要权限认证的路由 if(response.config.custom.auth){ if(response.data.code == 4011){ // 刷新 token jwt.setAccessToken(response.data.data.access_token); // 携带新 token 重新请求 let repeatRes = await http.request(response.config); if ( repeatRes ) { response = repeatRes; } } } return response}, (response) => { // 请求错误做点什么 if(response.statusCode == 401){ getApp().globalData.isLogin = false; uni.showToast({icon:'none',duration:2000,title: "请登录"}) }else if(response.statusCode == 403){ uni.showToast({ title: "您没有权限进行此项操作,请联系客服。", icon: "none" }); } return response})export { http}
全域掛載
import Vue from 'vue'import App from './App'import { http } from '@/utils/luch/index.js' //这里Vue.prototype.$http = http Vue.config.productionTip = falseApp.mpType = 'app'const app = new Vue({ ...App})app.$mount()
3.有關權限認證的js 封裝
authorize.js
篇幅原因,沒有貼完整的程式碼,其他並沒有使用到。例如 uni.checkSession(),由於使用 jwt 接手了小程式的登陸態,所以目前沒有用到這個方法。// #ifndef H5const loginCode = provider => { return new Promise((resolve, reject) => { uni.login({ provider: provider, success: function(loginRes) { if (loginRes && loginRes.code) { resolve(loginRes.code) } else { reject("获取code失败") } }, fail:function(){ reject("获取code失败")} }); })}// #endifexport { loginCode //登录获取code}登入後複製
jwt.js
專門管理 access_token 的,程式碼不多,同時將 userinfo 的管理也放在裡面。const tokenKey = 'accessToken';//键值const userKey = 'user'; // 用户信息// tokenconst getAccessToken = function(){ let token=''; try {token = 'Bearer '+ uni.getStorageSync(tokenKey);} catch (e) {} return token;}const setAccessToken = (access_token) => { try {uni.setStorageSync(tokenKey, access_token);return true;} catch (e) {return false;}}const clearAccessToken = function(){ try {uni.removeStorageSync(tokenKey);} catch (e) {}}// userinfoconst setUser = (user)=>{ try {uni.setStorageSync(userKey, user);return true;} catch (e) {return false;}}const getUser = function(){ try {return uni.getStorageSync(userKey)} catch (e) {return false;}}const clearUser = function(){ try {uni.removeStorageSync(userKey)} catch (e) {}}export default { getAccessToken,setAccessToken,clearAccessToken,getUser,setUser,clearUser}登入後複製
auth.js
只處理login ,為什麼單獨放在一個文件,沒別的,因為到處都用到import {loginCode} from '@/utils/auth/authorize.js';import jwt from '@/utils/auth/jwt.js';import {http} from '@/utils/luch/index.js';const login=function(detail){ return new Promise((resolve, reject) => { loginCode().then(code=>{ detail.code = code; return http.post('/v1/auth/login',detail); }) .then(res=>{ jwt.setAccessToken(res.data.data.access_token); jwt.setUser(res.data.data.user); getApp().globalData.isLogin = true; resolve(res.data.data.user); }) .catch(err=>{ reject('登陆失败') }) })}export default {login}登入後複製
4. laravel auth 中間件
這裡叨一點jwt-auth 方面的。 1,當一個token過期並進行了刷新token,那麼原token會被列在“黑名單”,即失效了。實際上 jwt-auth 也維護了一個檔案來儲存黑名單,而達到刷新時間上限才會清理失效的token。例如過期時間為10分鐘,刷新上限為一個月,這段期間會產生大量的黑名單,影響效能,所以盡量的調整,例如過期時間為60分鐘,刷新上限為兩週,或過期時間一周,刷新上限一個月都沒有問題的。 2,關於無痛刷新方案,當token過期時,我採用的前端兩次請求完成刷新,其中用戶是無感知的,網上有直接一次請求自動刷新並登陸的方案,我沒有採用,至於為什麼,沒別的,看不懂。不過我整理了各種 jwt 各種 exception ,需要的同學可以自訂。 TokenExpiredException 過期、TokenInvalidException 無法解析令牌、UnauthorizedHttpException 未攜帶令牌、JWTException 令牌失效或達到刷新上限或jwt內部錯誤。<?phpnamespace App\Http\Middleware;use App\Library\Y;use Closure;use Exception;use Tymon\JWTAuth\Exceptions\JWTException;use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;use Tymon\JWTAuth\Exceptions\TokenExpiredException;class ApiAuth extends BaseMiddleware{ public function handle($request, Closure $next, $guard = 'api') { // 在排除名单中 比如登录 if($request->is(...$this->except)){ return $next($request); } try { $this->checkForToken($request);// 是否携带令牌 if ( $this->auth->parseToken()->authenticate() ) { return $next($request); //验证通过 } }catch(Exception $e){ // 如果token 过期 if ($e instanceof TokenExpiredException) { try{ // 尝试刷新 如果成功 返给前端 关于前端如何处理的 看前边 index.js $token = $this->auth->refresh(); return Y::json(4011, $e->getMessage(),['access_token'=>$token]); }catch(JWTException $e){ // 达到刷新时间上限 return Y::json(401, $e->getMessage()); } }else{ // 其他各种 直接返回 401 状态码 不再细分 return Y::json(401, $e->getMessage()); } } } protected $except = [ 'v1/auth/login', ];}登入後複製
筆者認為這種刷新很不好維護,直接使用一次性token,過期直接重新登入比較好,視小程式或網站是否要求極強的安全性而定,一般不需求很高的安全性,https請求下一次性token更好,這裡的中間件只需要auth()->check(),true 即登錄狀態,false 即未登入。
#5. 取得手機號碼登陸<template>
<view>
<button>获取手机号</button>
<button>获取用户数据</button>
<button>清除用户数据</button>
</view></template><script>
import auth from '@/utils/auth/auth.js';
import jwt from '@/utils/auth/jwt.js';
var _self;
export default{
data() {return {}},
onLoad(option) {},
onShow(){},
methods: {
decryptPhoneNumber: function(e){
// console.log(e.detail);
if( e.detail.errMsg == "getPhoneNumber:ok" ){ //成功
auth.login(e.detail);
}
},
me: function(){
this.$http.get('/v1/auth/me',{custom: {auth: true}}).then(res=>{
console.log(res,'success')
}).catch(err=>{
console.log(err,'error60')
})
},
clear: function(){
jwt.clearAccessToken();
jwt.clearUser();
uni.showToast({
icon: 'success',
title: '清除成功',
duration:2000,
});
}
},
components: {}
}</script><style></style>
登入後複製
<template> <view> <button>获取手机号</button> <button>获取用户数据</button> <button>清除用户数据</button> </view></template><script> import auth from '@/utils/auth/auth.js'; import jwt from '@/utils/auth/jwt.js'; var _self; export default{ data() {return {}}, onLoad(option) {}, onShow(){}, methods: { decryptPhoneNumber: function(e){ // console.log(e.detail); if( e.detail.errMsg == "getPhoneNumber:ok" ){ //成功 auth.login(e.detail); } }, me: function(){ this.$http.get('/v1/auth/me',{custom: {auth: true}}).then(res=>{ console.log(res,'success') }).catch(err=>{ console.log(err,'error60') }) }, clear: function(){ jwt.clearAccessToken(); jwt.clearUser(); uni.showToast({ icon: 'success', title: '清除成功', duration:2000, }); } }, components: {} }</script><style></style>
1 後端
// 登陆 public function login(Request $request) { $platform = $request->header('platform'); if(!$platform || !in_array($platform,User::$platforms)){ return Y::json(1001, '不支持的平台类型'); } $post = $request->only(['encryptedData', 'iv', 'code']); $validator = Validator::make($post, [ 'encryptedData' => 'required', 'iv' => 'required', 'code' => 'required' ]); if ($validator->fails()) {return Y::json(1002,'非法请求');} switch ($platform) { case 'MP-BAIDU': $decryption = (new BdDataDecrypt())->decrypt($post['encryptedData'],$post['iv'],$post['code']); break; default: $decryption = false; break; } // var_dump($decryption); if($decryption !== false){ $user = User::where('u_platform',$platform)->where('u_mobile',$decryption['mobile'])->first(); if($user){ $user->u_login_time = date('Y-m-d H:i:s',time()); $user->save(); }else{ $user = User::create([ 'u_username'=> substr_replace($decryption['mobile'],'******',3,6), 'u_nickname'=> User::crateNickName(), 'u_platform'=> $platform, 'u_mobile' => $decryption['mobile'], 'u_openid' => $decryption['openid'], 'u_regtime' => date('Y-m-d H:i:s',time()) ]); } $token = auth()->login($user); return Y::json( array_merge( $this->respondWithToken($token), ['user'=>['nickName'=>$user->u_nickname]] ) ); } return Y::json(1003,'登录失败'); } // 返回 token protected function respondWithToken($token) { return ['access_token' => $token]; }
手機號碼解密
<?phpnamespace App\Library;use App\Library\Y;class BdDataDecrypt{ private $_appid; private $_app_key; private $_secret; private $_session_key; public function __construct() { $this->_appid = env('BD_APPID'); $this->_app_key = env('BAIDU_KEY'); $this->_secret = env('BD_SECRET'); } public function decrypt($encryptedData, $iv, $code){ $res = $this->getSessionKey($code); if($res === false){return false;} $data['openid'] = $res['openid']; $res = $this->handle($encryptedData,$iv,$this->_app_key,$res['session_key']); if($res === false){return false;} $res = json_decode($res,true); $data['mobile'] = $res['mobile']; return $data; } public function getSessionKey($code) { $params['code'] = $code; $params['client_id'] = $this->_app_key; $params['sk'] = $this->_secret; $res = Y::curl("https://spapi.baidu.com/oauth/jscode2sessionkey",$params,0,1); // var_dump($res); /** * 错误返回 * array(3) { ["errno"]=> int(1104) ["error"]=> string(33) "invalid code , expired or revoked" ["error_description"]=> string(33) "invalid code , expired or revoked" } 成功返回: array(2) { ["openid"]=> string(26) "z45QjEfvkUJFwYlVcpjwST5G8w" ["session_key"]=> string(32) "51b9297ababbcf43c1a099256bf82d75" } */ if( isset($res['error']) ){ return false; } return $res; } /** * 官方 demo * return string(24) "{"mobile":"18288881111"}" or false */ private function handle($ciphertext, $iv, $app_key, $session_key) { $session_key = base64_decode($session_key); $iv = base64_decode($iv); $ciphertext = base64_decode($ciphertext); $plaintext = false; if (function_exists("openssl_decrypt")) { $plaintext = openssl_decrypt($ciphertext, "AES-192-CBC", $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); } else { $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null); mcrypt_generic_init($td, $session_key, $iv); $plaintext = mdecrypt_generic($td, $ciphertext); mcrypt_generic_deinit($td); mcrypt_module_close($td); } if ($plaintext == false) { return false; } // trim pkcs#7 padding $pad = ord(substr($plaintext, -1)); $pad = ($pad 32) ? 0 : $pad; $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad); $plaintext = substr($plaintext, 16); $unpack = unpack("Nlen/", substr($plaintext, 0, 4)); $content = substr($plaintext, 4, $unpack['len']); $app_key_decode = substr($plaintext, $unpack['len'] + 4); return $app_key == $app_key_decode ? $content : false; }}
6. 無痛刷新access_token 想法
先說我使用的方法是,後端判斷token 過期後,自動嘗試刷新,刷新成功返回新的token,前端在回應攔截器裡,捕獲到後端回應的約定code,把新的token 存儲,並且緊接著二次請求,最終感知上是一次正常的請求。另一個思路,後端嘗試刷新成功後,自動為當前用戶登陸,並在 header 中返回新 token,前端只負責儲存。
7. 小程序如何判断登陆状态
其实思路也很简单,非前后端分离怎么做的,前后端分离就怎么做,原理一样。非前后端分离,在每次请求时都会读取 session ,那么前后端分离,更好一些,有些公开请求不走中间件,也就无需判断登陆态,只有在需要权限认证的页面,在页面初始化时发出一次请求走中间件,以此判断登陆状态。
定义全局登陆检查函数
import jwt from '@/utils/auth/jwt.js';Vue.prototype.checkLogin = function(){ var TOKEN = jwt.getAccessToken(); return new Promise((resolve, reject) => { if(TOKEN){ http.get('/v1/auth/check',{custom: {auth: true}}).then(res=>{ // 通过中间件 一定是登陆态 resolve(true); }).catch(err=>{ resolve(false); console.log(err) // 这里是401 403 后端500错误或者网络不好 }) }else{ resolve(false) //没有token 一定是未登陆 } })}
笔者最终放弃上面的这种检查登录的方式,直接检验storage中有user和token即视为登录状态。以被动的验证代替主动去验证,就是说用户执行一个请求,返回401,那么就改变登录状态。以后再补充。
前端
<script> export default { data() { return { isLogin:null } }, onLoad() { this.checkLogin().then(loginStatus=>{ this.isLogin = loginStatus; }); }, methods: { }, components: {} }</script>
以上是uni-app 小程式 Laravel+jwt 權限認證系列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

隨著行動互聯網技術和智慧型手機的普及,微信成為了人們生活中不可或缺的一個應用。而微信小程式則讓人們可以在不需要下載安裝應用程式的情況下,直接使用小程式來解決一些簡單的需求。本文將介紹如何使用Python來開發微信小程式。一、準備工作在使用Python開發微信小程式之前,需要先安裝相關的Python函式庫。這裡推薦使用wxpy和itchat這兩個函式庫。 wxpy是一個微信機器

小程式能用react,其使用方法:1、基於「react-reconciler」實作一個渲染器,產生一個DSL;2、建立一個小程式元件,去解析和渲染DSL;3、安裝npm,並執行開發者工具中的建構npm;4、在自己的頁面中引入包,再利用api即可完成開發。

實作微信小程式中的卡片翻轉特效在微信小程式中,實現卡片翻轉特效是一種常見的動畫效果,可以提升使用者體驗和介面互動的吸引力。以下將具體介紹如何在微信小程式中實現卡片翻轉的特效,並提供相關程式碼範例。首先,需要在小程式的頁面佈局檔案中定義兩個卡片元素,一個用於顯示正面內容,一個用於顯示背面內容,具體範例程式碼如下:<!--index.wxml-->&l

VSCode中如何開發uni-app?以下這篇文章跟大家分享一下VSCode中開發uni-app的教學課程,這可能是最好、最詳細的教學了。快來看看!

本站10月31日消息,今年5月27日,螞蟻集團宣布啟動“漢字拾光計劃”,最近又迎來新進展:支付寶上線“漢字拾光-生僻字”小程序,用於向社會徵集生僻字,補充生僻字庫,同時提供不同的生僻字輸入體驗,以幫助完善支付寶內的生僻字輸入方法。目前,用戶搜尋「漢字拾光」、「生僻字」等關鍵字就可以進入「生僻字」小程式。在小程式裡,使用者可以提交尚未被系統辨識輸入的生僻字圖片,支付寶工程師確認後,將會對字庫進行補錄入。本站注意到,使用者也可以在小程式體驗最新的拆字輸入法,這項輸入法針對讀音不明確的生僻字設計。用戶拆

uniapp如何實現小程式和H5的快速轉換,需要具體程式碼範例近年來,隨著行動網路的發展和智慧型手機的普及,小程式和H5成為了不可或缺的應用形式。而uniapp作為一個跨平台的開發框架,可以在一套程式碼的基礎上,快速實現小程式和H5的轉換,大大提高了開發效率。本文將介紹uniapp如何實現小程式和H5的快速轉換,並給出具體的程式碼範例。一、uniapp簡介unia

如何利用uniapp開發一個貪吃蛇小遊戲?以下這篇文章就手把手帶大家在uniapp中實現貪吃蛇小遊戲,希望對大家有幫助!
