采取Jwt-Auth达成API客户认证和无感刷新访问权限

原创
小哥 3年前 (2022-11-02) 阅读数 186 #PHP
文章标签 PHPLaravel

<?php
namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

// 请注意,我们希望继承的是 jwt 的 BaseMiddleware
class RefreshToken extends BaseMiddleware
{
/**

  • Handle an incoming request.
  • @param  \Illuminate\Http\Request $request
  • @param  \Closure $next
  • @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
  • @return mixed
    */
    public function handle($request, Closure $next)
    {
    // 检查此请求是否已 token如果不是,则抛出异常。
    $this->checkForToken($request);
    // 使用 try 要捕获的包 token 引发的过期消息 TokenExpiredException  异常
    try {
    if ($this->auth->parseToken()->authenticate()) {
    return $next($request);
    }
    throw new UnauthorizedHttpException(jwt-auth, 未登录);
    } catch (UnauthorizedHttpException $exception){
    return response()->json([code=>401,msg=>信息错误,请重新登录,result=>]);
    } catch (TokenExpiredException $exception) {
    // 它是在这里被捕获的。 token 引发的过期消息 TokenExpiredException 异常,我们在这里需要做的是刷新用户的 token 并将其添加到响应头
    try {
    // 刷新用户的 token
    $token = $this->auth->refresh();
    // 使用一次性登录以确保此请求成功。
    Auth::guard(api)->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()[sub]);
    } catch (JWTException $exception) {
    // 如果捕获到此异常,则表示。 refresh 它也已经过期了。用户无法刷新令牌,需要重新登录。
    return response()->json([code=>401,msg=>信息错误,请重新登录,result=>]);
    }
    }catch(\Exception $e){
    return response()->json([code=>401,msg=>信息错误,请重新登录,result=>]);
    }

// 在响应头中返回新的。。 token
return $this->setAuthenticationHeader($next($request), $token);
}
}


<?php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Models\Api\Users;

class AuthController extends Controller
{

/**

  • Get a JWT token via given credentials.
  • @param  \Illuminate\Http\Request $request
  • @return \Illuminate\Http\JsonResponse
  • https://deguo.jitaogo.top/api/auth/login
    */
    public function login(Request $request)
    {
    $code = $request->get(code,);
    if($code == ){
    return response()->json([code=>400,msg=>code不能为空,result=>]);
    }
    $mini_program_app = app(wechat.mini_program);
    $res = $mini_program_app->auth->session($code);
    $open_id = $res[openid];

$datetime = date(Y-m-d H:i:s);
if($open_id){
$user = Users::where(open_id,$open_id)->first();
if(!$user){
$arr[open_id] = $open_id;
$arr[created_at] = $datetime;
$arr[updated_at] = $datetime;
if(Users::create($arr)){
$user = Users::where(open_id,$open_id)->first();
}
}
$token = bearer .Auth::guard(api)->fromUser($user);
return response()->json([code=>200,msg=>登录成功,result=>$token]);
}else{
return response()->json([code=>400,msg=>用户信息错误,result=>]);
}

}

/**

  • 正在处理用户注销逻辑
  • @return \Illuminate\Http\JsonResponse
    */
    public function logout()
    {
    Auth::guard(api)->logout();
    return response()->json([code=>200,msg=>退出成功,result=>]);

}
}


//index.js

//获取应用程序实例

const app = getApp()

Page({

data: {

motto: Hello World,

userInfo: {},

hasUserInfo: false,

canIUse: wx.canIUse(button.open-type.getUserInfo)

},

//事件处理程序

bindViewTap: function() {

wx.navigateTo({

url: ../logs/logs

})

},

onLoad: function () {

// if (app.globalData.userInfo) {

//   this.setData({

//     userInfo: app.globalData.userInfo,

//     hasUserInfo: true

//   })

// } else if (this.data.canIUse){

//   // 由于 getUserInfo 是网络请求,并且可能在 Page.onLoad 只有在回来之后

//   // 所以加入这里吧 callback 为了防止这种情况发生

//   app.userInfoReadyCallback = res => {

//     this.setData({

//       userInfo: res.userInfo,

//       hasUserInfo: true

//     })

//   }

// } else {

//   // 在没有 open-type=getUserInfo 版本兼容性处理

//   wx.getUserInfo({

//     success: res => {

//       app.globalData.userInfo = res.userInfo

//       this.setData({

//         userInfo: res.userInfo,

//         hasUserInfo: true

//       })

//     }

//   })

// }

},

onShow(){

if(!wx.getStorageSync(token)){

wx.login({

success (res) {

if (res.code) {

wx.request({

url: https://*************/api/auth/login,

data: {

code: res.code

},

header: {

content-type: application/json // 默认值

},

method:POST,

success (res) {

if(res.data.code == 200){

wx.setStorageSync(token, res.data.result);

}

console.log(res)

}

})

} else {

console.log(登录失败! + res.errMsg)

}

}

})

}

},

getInfo(){

wx.request({

url: https://*************/api/user/index,

data: {

},

header: {

Content-type: application/json,

Authorization:wx.getStorageSync(token)

},

method:GET,

success (res) {

if(res.header.Authorization){

wx.setStorageSync(token, res.header.Authorization);

}

console.log(res)

}

})

},

getUserInfo: function(e) {

console.log(e)

app.globalData.userInfo = e.detail.userInfo

this.setData({

userInfo: e.detail.userInfo,

hasUserInfo: true

})

}

})


安装

jwt-auth 最新版本是 1.0.0 rc.1 版本,已受支持 Laravel 5.5 。如果您使用的是 Laravel 5.5 版本,可以使用以下命令安装。根据评论区 @tradzero 兄弟的建议,如果你是的话。 Laravel 5.5 以下版本也建议使用最新版本,RC.1 以前的版本都有多个用户。token认证的安全问题。

$ composer require tymon/jwt-auth 1.0.0-rc.1
复制代码

配置

添加服务提供商

将以下行添加到 config/app.php 文件 providers 数组中:

app.php

providers => [

    ...

    TymonJWTAuthProvidersLaravelServiceProvider::class,
]
复制代码

发布配置文件

在你的 shell 运行以下命令Release。 jwt-auth 简介:

shell

$ php artisan vendor:publish --provider="TymonJWTAuthProvidersLaravelServiceProvider"
复制代码

此命令将出现在。 config 将生成一个目录。 jwt.php 配置文件中,您可以在此处自定义配置。

生成密钥

jwt-auth 一个预定义的 Artisan 命令可以方便地生成 Secret,你只需要穿上你的 shell 只需在中运行以下命令:

shell

$ php artisan jwt:secret
复制代码

此命令将出现在。你的 .env 在文件中添加新行 JWT_SECRET=secret

配置 Auth guard

config/auth.php 文件,你需要把。 guards/driver 更新为 jwt

auth.php

defaults => [
    guard => api,
    passwords => users,
],

...

guards => [
    api => [
        driver => jwt,
        provider => users,
    ],
],
复制代码

仅限于在使用中 Laravel 5.2 和以上只能使用。

更改 Model

如果您需要使用 jwt-auth 作为用户身份验证,我们需要在我们的 User 在模型中实现一个接口的一个小更改,更改后。 User 模型如下:

User.php

getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}
复制代码

配置项详细信息

jwt.php

 env(JWT_SECRET),

    /*
    |--------------------------------------------------------------------------
    | JWT Authentication Keys
    |--------------------------------------------------------------------------
    |
    | 如果你在 .env 该文件定义了 JWT_SECRET 随机字符串
    | 那么 jwt 将会使用 对称算法 来生成 token
    | 如果你没有,那么jwt 将使用如下配置的公钥和私钥生成 token
    |
    */

    keys => [

        /*
        |--------------------------------------------------------------------------
        | Public Key
        |--------------------------------------------------------------------------
        |
        | 公钥
        |
        */

        public => env(JWT_PUBLIC_KEY),

        /*
        |--------------------------------------------------------------------------
        | Private Key
        |--------------------------------------------------------------------------
        |
        | 私钥
        |
        */

        private => env(JWT_PRIVATE_KEY),

        /*
        |--------------------------------------------------------------------------
        | Passphrase
        |--------------------------------------------------------------------------
        |
        | 私钥的密码。 如果未设置,则可以设置。 null。
        |
        */

        passphrase => env(JWT_PASSPHRASE),

    ],

    /*
    |--------------------------------------------------------------------------
    | JWT time to live
    |--------------------------------------------------------------------------
    |
    | 指定 access_token 有效时间长度(以分钟为单位),默认为1小时,您也可以将其设置为空,以生成永不过期的标签。
    |
    */

    ttl => env(JWT_TTL, 60),

    /*
    |--------------------------------------------------------------------------
    | Refresh time to live
    |--------------------------------------------------------------------------
    |
    | 指定 access_token 可以刷新的时间长度(分钟)。默认时间为 2 周。
    | 这可能意味着,如果用户有一个 access_token,然后他就可以拿着他的 access_token 
    | 来领取新的吧 access_token,直到 2 一周后,他无法继续刷新,需要重新登录。
    |
    */

    refresh_ttl => env(JWT_REFRESH_TTL, 20160),

    /*
    |--------------------------------------------------------------------------
    | JWT hashing algorithm
    |--------------------------------------------------------------------------
    |
    | 指定将用于对令牌签名的哈希算法。
    |
    */

    algo => env(JWT_ALGO, HS256),

    /*
    |--------------------------------------------------------------------------
    | Required Claims
    |--------------------------------------------------------------------------
    |
    | 指定必须存在于任何标记中的声明。
    | 
    |
    */

    required_claims => [
        iss,
        iat,
        exp,
        nbf,
        sub,
        jti,
    ],

    /*
    |--------------------------------------------------------------------------
    | Persistent Claims
    |--------------------------------------------------------------------------
    |
    | 指定刷新令牌时要保留的声明密钥。
    |
    */

    persistent_claims => [
        // foo,
        // bar,
    ],

    /*
    |--------------------------------------------------------------------------
    | Blacklist Enabled
    |--------------------------------------------------------------------------
    |
    | 要使令牌无效,您必须启用黑名单。
    | 如果您不想要或不需要此功能,请设置它 false。
    |
    */

    blacklist_enabled => env(JWT_BLACKLIST_ENABLED, true),

    /*
    | -------------------------------------------------------------------------
    | Blacklist Grace Period
    | -------------------------------------------------------------------------
    |
    | 当多个并发请求使用相同的JWT进行时,
    | 由于 access_token 的刷新 ,其中一些可能会失败
    | 以秒为单位设置请求时间,以防止并发请求失败。
    |
    */

    blacklist_grace_period => env(JWT_BLACKLIST_GRACE_PERIOD, 0),

    /*
    |--------------------------------------------------------------------------
    | Providers
    |--------------------------------------------------------------------------
    |
    | 指定在整个包中使用的各种提供程序。
    |
    */

    providers => [

        /*
        |--------------------------------------------------------------------------
        | JWT Provider
        |--------------------------------------------------------------------------
        |
        | 指定用于创建和解码令牌的提供程序。
        |
        */

        jwt => TymonJWTAuthProvidersJWTNamshi::class,

        /*
        |--------------------------------------------------------------------------
        | Authentication Provider
        |--------------------------------------------------------------------------
        |
        | 指定用于对用户进行身份验证的提供程序。
        |
        */

        auth => TymonJWTAuthProvidersAuthIlluminate::class,

        /*
        |--------------------------------------------------------------------------
        | Storage Provider
        |--------------------------------------------------------------------------
        |
        | 指定用于在黑名单中存储标记的提供程序。
        |
        */

        storage => TymonJWTAuthProvidersStorageIlluminate::class,

    ],

];
复制代码

自定义身份验证中间件

首先,让我们解释一下我想要实现的目标。我希望用户能提供他们的帐户密码来登录。如果登录成功,则我将发出 access _token ,设置在 header 若要请求需要用户身份验证的路由,请执行以下操作。

同时,我希望如果用户的令牌过期,我可以临时传递这个请求并刷新用户的令牌 access _token ,最后,在响应头中,新的 access _token 回到前端,可以毫无痛苦地恢复精神。 access _token ,用户可以获得良好的体验,因此开始编写代码。

执行以下命令以创建新的中间件:

php artisan make:middleware RefreshToken
复制代码

中间件代码如下:

RefreshToken .php

checkForToken($request);

       // 使用 try 要捕获的包 token 引发的过期消息 TokenExpiredException  异常
        try {
            // 检测用户登录状态,正常通过。
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException(jwt-auth, 未登录);
        } catch (TokenExpiredException $exception) {
          // 它是在这里被捕获的。 token 引发的过期消息 TokenExpiredException 异常,我们在这里需要做的是刷新用户的 token 并将其添加到响应头
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
               // 使用一次性登录以确保此请求成功。
                Auth::guard(api)->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()[sub]);
            } catch (JWTException $exception) {
               // 如果捕获到此异常,则表示。 refresh 它也已经过期了。用户无法刷新令牌,需要重新登录。
                throw new UnauthorizedHttpException(jwt-auth, $exception->getMessage());
            }
        }

        // 在响应头中返回新的。。 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
}

复制代码

设置 Axios 拦截器

我选用的 HTTP 所请求的套间是 axios 。为了实现无痛清爽 token 效果,我们需要 axios 定义一个拦截器来接收我们的刷新。 Token,代码如下:

app.js

import Vue from vue
import router from ./router
import store from ./store
import iView from iview
import iview/dist/styles/iview.css

Vue.use(iView)

new Vue({
    el: #app,
    router,
    store,
    created() {
        // 自定义的 axios 响应拦截器
        this.$axios.interceptors.response.use((response) => {
            // 确定响应中是否有一个。 token,如果有,则直接使用此命令 token 替换本地的 token。您可以根据您的业务需求编写自己的更新。 token 的逻辑
            var token = response.headers.authorization
            if (token) {
                // 如果 header 中存在 token,然后触发 refreshToken 方法,替换本地 token
                this.$store.dispatch(refreshToken, token)
            }
            return response
        }, (error) => {
            switch (error.response.status) {

                // 如果在响应中 http code 为 401,则该用户可以 token 如果它失败了什么的,我就会触发它。 logout 方法,清除本地数据并将用户重定向到登录页。
                case 401:
                    return this.$store.dispatch(logout)
                    break
                // 如果在响应中 http code 为 400,则向用户弹出错误提示。
                case 400:
                    return this.$Message.error(error.response.data.error)
                    break
            }
            return Promise.reject(error)
        })
    }
})

复制代码

Vuex 其中的代码如下:

store/index.js

import Vue from vue
import Vuex from vuex
import axios from axios

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        name: null,
        avatar: null,
        mobile: null,
        token: null,
        remark: null,
        auth: false,
    },
    mutations: {
        // 用户登录成功并存储 token 并设置 header 头
        logined(state, token) {
            state.auth = true
            state.token = token
            localStorage.token = token
        },
        // 用户刷新 token 成功,用新的 token 替换本地的token
        refreshToken(state, token) {
            state.token = token
            localStorage.token = token
            axios.defaults.headers.common[Authorization] = state.token
        },
        // 登录成功后,拉取用户信息并存储在本地。
        profile(state, data) {
            state.name = data.name
            state.mobile = data.mobile
            state.avatar = data.avatar
            state.remark = data.remark
        },
        // 用户注销,清除本地数据
        logout(state){
            state.name = null
            state.mobile = null
            state.avatar = null
            state.remark = null
            state.auth = false
            state.token = null

            localStorage.removeItem(token)
        }
    },
    actions: {
         // 成功登录后保存用户信息
        logined({dispatch,commit}, token) {
            return new Promise(function (resolve, reject) {
                commit(logined, token)
                axios.defaults.headers.common[Authorization] = token
                dispatch(profile).then(() => {
                    resolve()
                }).catch(() => {
                    reject()
                })
            })
        },
        // 登录成功后使用 token 拉取用户信息
        profile({commit}) {
            return new Promise(function (resolve, reject) {
                axios.get(profile, {}).then(respond => {
                    if (respond.status == 200) {
                        commit(profile, respond.data)
                        resolve()
                    } else {
                        reject()
                    }
                })
            })
        },
        // 用户注销,清除本地数据并重定向至登录页面
        logout({commit}) {
            return new Promise(function (resolve, reject) {
                commit(logout)
                axios.post(auth/logout, {}).then(respond => {
                    Vue.$router.push({name:login})
                })
            })
        },
        // 将刷新的 token 保存到本地
        refreshToken({commit},token) {
            return new Promise(function (resolve, reject) {
                commit(refreshToken, token)
            })
        },
    }
})

复制代码

更新异常处理 Handler

因为我们构建了 api 服务,所以我们需要更新它。 app/Exceptions/Handler.php 中的 render

方法,自定义处理某些异常。

Handler.php

 array_first(array_collapse($exception->errors()))], 400);
        }
        // 用户身份验证异常,我们需要返回。 401 的 http code 和错误消息
        if ($exception instanceof UnauthorizedHttpException) {
            return response($exception->getMessage(), 401);
        }

        return parent::render($request, $exception);
    }
}

复制代码

更新此方法后,上面的自定义中间件中抛出的异常和下面的参数验证错误引发的异常将以指定的格式抛出。

使用

现在,我们可以在我们的 routes/api.php 已向路由文件中添加了几条新路由以进行测试:

api.php

Route::prefix(auth)->group(function($router) {
    $router->post(login, AuthController@login);
    $router->post(logout, AuthController@logout);

});

Route::middleware(refresh.token)->group(function($router) {
    $router->get(profile,UserController@profile);
});
复制代码

在你的 shel l 运行以下命令以添加新控制器:

$ php artisan make:controller AuthController
复制代码

打开此控制器并编写以下内容

 [
                required,
                exists:users,
            ],
            password => required|string|min:6|max:20,
         ];

        // 验证参数,如果验证失败则抛出该参数。 ValidationException 的异常
        $params = $this->validate($request, $rules);

       // 使用 Auth 登录用户,登录成功返回 201 的 code 和 token,如果登录失败则返回
        return ($token = Auth::guard(api)->attempt($params))
            ? response([token => bearer  . $token], 201)
            : response([error => 帐号或密码错误], 400);
    }

    /**
     * 正在处理用户注销逻辑
     *
     * @return IlluminateHttpJsonResponse
     */
    public function logout()
    {
        Auth::guard(api)->logout();

        return response([message => 退出成功]);
    }
}
版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门