>웹 프론트엔드 >uni-app >uni-app 애플릿 Laravel+jwt 권한 인증 시리즈

uni-app 애플릿 Laravel+jwt 권한 인증 시리즈

coldplay.xixi
coldplay.xixi앞으로
2020-11-27 17:15:5512249검색

uni-app 개발 튜토리얼 칼럼에서는 일련의 권한 인증 방법을 소개합니다. 내가 추천하는 것:

uni-app 애플릿 Laravel+jwt 권한 인증 시리즈

Uni-APP 개발 튜토리얼 환경 설명 Uni-APP
Laravel 5.7 + jwt -auth 1.0.0

권한 인증에 대한 전반적인 설명

  1. 디자인 테이블 구조
  2. 프런트엔드 요청 클래스
  3. js 패키지 권한 인증과 관련된 비인식 새로고침 토큰이 포함되어 있습니다.
  4. laravel 인증 미들웨어에 비인식 새로고침 토큰이 포함되어 있습니다.
  5. 로그인을 위한 휴대폰 번호 가져오기
  6. 손쉬운 새로고침 access_token 아이디어
  7. 미니 프로그램은 로그인 상태를 어떻게 결정하나요?

uni-app
laravel 5.7 + jwt-auth 1.0.0

权限认证整体说明

  1. 设计表结构
  2. 前端 request 类
  3. 有关权限认证的 js 封装 包含无感知刷新 token
  4. laravel auth 中间件 包含无感知刷新 token
  5. 获取手机号登陆
  6. 无痛刷新 access_token 思路
  7. 小程序如何判断登陆状态

设计表结构

和一般设计表没有什么区别,如果是多平台小程序,通过 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 애플릿 Laravel+jwt 권한 인증 시리즈

는 일반 디자인 테이블과 다르지 않습니다. 이는 다중 플랫폼 미니 프로그램이며 account_id를 통해 공동 테이블을 연결합니다.

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}

2. 프론트엔드 요청 클래스

비교적 좋은 요청 클래스 luch-request는 동적으로 수정되는 구성과 인터셉터를 지원하며 유니앱 플러그인 마켓에서 찾을 수 있습니다. .

request.js는 변경할 필요가 없습니다. 사용자 정의 논리는 index.js에 있습니다.

index.js

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()
Global mount

// #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}

3.js 권한 인증 관련 패키지

authorize.js

공간상의 이유로 전체 코드는 게시되지 않습니다. 도착하는 데 사용되지 않습니다. 예를 들어, uni.checkSession()은 jwt가 애플릿의 로그인 상태를 인계받는 데 사용되므로 이 메소드는 현재 사용되지 않습니다.

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}
jwt.js

는 별다른 코드 없이 access_token을 관리할 수 있도록 특별히 설계되었으며 사용자 정보 관리 기능도 담았습니다.
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}

auth.js

로그인만 처리하는데 왜 별도의 파일에 넣지 않고 모든 곳에서 사용되기 때문입니다

<?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 = &#39;api&#39;)
    {
        // 在排除名单中 比如登录
        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',
    ];}

4 laravel auth middleware

약간. 여기에서 jwt-auth 측면에 대해 이야기해 보세요. 1. 토큰이 만료되고 토큰이 새로 고쳐지면 원래 토큰이 "블랙리스트"에 나열되고 무효화됩니다. 실제로 jwt-auth는 블랙리스트를 저장하는 파일도 유지관리하며 유효하지 않은 토큰은 새로 고침 시간 제한에 도달한 경우에만 삭제됩니다. 예를 들어 만료 시간은 10분이고 새로 고침 제한은 1개월입니다. 이 기간 동안 블랙리스트가 대량으로 생성되어 성능에 영향을 미치므로 만료 시간 등을 최대한 조정해 보세요. 시간은 60분, 새로 고침 제한은 2주 또는 만료 시간은 1주일이고 새로 고침 제한은 한 달 동안 문제 없음입니다. 2. 무통 새로 고침 솔루션에 관해서는 토큰이 만료되면 제가 사용하는 프런트 엔드에서 새로 고침을 완료하기 위해 두 가지 요청을 하는데, 사용자는 이를 인식하지 못하고 인터넷에 직접 자동 새로 고침 및 로그인을 요청하는 솔루션이 있습니다. 그것을 사용하지 않았습니다. 이유는 다른 것을 이해할 수 없습니다. 그러나 나는 다양한 jwt 예외를 컴파일했으며 학생들은 필요한 경우 이를 사용자 정의할 수 있습니다. TokenExpiredException이 만료되고 TokenInvalidException이 토큰을 구문 분석할 수 없으며 UnauthorizedHttpException이 토큰을 전달하지 않습니다. JWTException 토큰이 만료되거나 새로 고침 제한에 도달하거나 jwt 내부 오류가 발생합니다.

<template>
    <view>
        <button>获取手机号</button>
        <button>获取用户数据</button>
        <button>清除用户数据</button>
    </view></template><script>
    import auth from &#39;@/utils/auth/auth.js&#39;;
    import jwt from &#39;@/utils/auth/jwt.js&#39;;
    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(&#39;/v1/auth/me&#39;,{custom: {auth: true}}).then(res=>{
                    console.log(res,&#39;success&#39;)
                }).catch(err=>{
                    console.log(err,&#39;error60&#39;)
                })
            },
            clear: function(){
                jwt.clearAccessToken();
                jwt.clearUser();
                uni.showToast({
                    icon: &#39;success&#39;,
                    title: &#39;清除成功&#39;,
                    duration:2000,
                });
            }
        },
        components: {}
    }</script><style></style>

저자는 이런 새로고침은 유지관리가 매우 어렵다고 생각합니다. 일회용 토큰을 직접 사용하는 것이 더 좋으며 만료 후 다시 로그인하는 것이 좋습니다. 일반적으로 높은 보안이 필요하지 않습니다. 다음 번 https 요청 시 일회용 토큰을 사용하는 것이 좋습니다. 여기서 미들웨어는 auth()->check()만 필요하며 true는 로그인 상태를 의미합니다. , false는 로그인되지 않았음을 의미합니다.

5. 로그인할 휴대폰 번호 받기
// 登陆
    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];
    }

Backend

<?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;
    }}
🎜🎜휴대폰 번호 복호화🎜🎜
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 一定是未登陆
        }
    })}
🎜🎜🎜6. 우선 제가 사용하는 방법은. is, backend 토큰이 만료되었다고 판단한 후 자동으로 새로 고침을 시도합니다. 새로 고침에 성공하면 새 토큰이 반환됩니다. 응답 인터셉터에서 프런트 엔드는 백엔드 응답의 합의된 코드를 캡처하고 새 토큰을 저장합니다. , 그리고 두 번째 요청을 하면 결국 1번으로 인식됩니다. 🎜 또 다른 사고 방식은 백엔드가 성공적으로 새로 고침을 시도한 후 자동으로 현재 사용자에 대해 로그인하고 헤더에 새 토큰을 반환한다는 것입니다. 🎜

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제