在构建REST-api时,由于api是无状态的,因此无法使用Cookie-Session的传统的Web认证方式,
使用Cookie-Session认证机制,服务端在接收到HTTP请求时会将客户端的Cookie与服务端的Session进行判别来进行认证。但是构建API时一般不采用此方案,而是采用token进行认证,JWT全称为JSON Web Token,就是一套实现标准:
jwt.io
前几天在使用Laravel构建一个前后端完全分离的Web App时,Laravel就只需要接受api请求并返回json格式的结果即可,连blade模板也完全不需要使用了,为了区别不同用户已经是否登陆的用户的请求,在Laravel中我就使用了jwt,具体使用的是这个包jwt-auth - Github,下面我就在以官方操作文档简单解释一下我是如何在Laravel中使用jwt-auth的
安装依赖
到我写这篇文章的今天:2018-2-8,这个包的1.0还是出去rc状态,应该马上会有stable版本了。官方Wiki说明Laravel 5.*
&& <5.5
最好使用0.5版本,1.0版本是为Laravel5.5所适配的,我目前使用的是Laravel 5.5 因此将1.0.0-rc.1加入依赖
composer require tymon/jwt-auth 1.0.0-rc.1
添加 Service Provider
使用Laravel5.4及以下版本时需要在在config/app.php
中添加其Service Provider,
'providers' => [
...
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
发布配置文件
执行以下命令
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
后会发现在config
目录会多出一个jwt.php
的配置文件,用于修改使用配置,比如token失效时间,刷新周期等等,这里我使用默认配置不做修改。
生成 Secret Key
执行以下命令
php artisan jwt:secret
此命令会在.env
文件中生成一个自己的私钥,用于为token签名
在应用部署到生产环境中时,需要在生产环再次执行此命令重新生成key
修改 User Model
在User
Model后实现Tymon\JWTAuth\Contracts\JWTSubject
,以实现jwt-auth的两个方法,方法分别为getJWTIdentifier()
和getJWTCustomClaims()
修改后的User Model应如下:
<?php
namespace App;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements JWTSubject
{
...
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}
修改默认 Auth Guard
由于构建的是一个完全后端分离的app,Laravel只需要实现api即可,因此不再使用web认证,于是将默认guard修改为api:在config/auth.php
中修改默认的Guard方式为api,再将api认证驱动修改为jwt
,如下
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
...
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
当然如果有时还是需要使用传统的Laravel的web guard时,可以在相应的Controller中添加如下方法即可避免使用默认的api guard:
protected function guard()
{
return Auth::guard('web');
}
添加路由
由于构建的是api服务,因此在routes/api.php
,中修改路由,在这里使用的路由默认uri最前面需加上api/
Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {
Route::post('login', 'AuthController@login');
Route::post('logout', 'AuthController@logout');
Route::post('refresh', 'AuthController@refresh');
Route::post('me', 'AuthController@me');
});
新建 Controller
这里也使用官方文档中的默认Controller,这一步完成之后,Laravel的jwt-auth就安装完成可以使用了
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* Get the authenticated User.
*
* @return \Illuminate\Http\JsonResponse
*/
public function me()
{
return response()->json(auth()->user());
}
/**
* Log the user out (Invalidate the token).
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* Refresh a token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth()->refresh());
}
/**
* Get the token array structure.
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]);
}
}
使用
发送一个post请求附带有效的用户email与密码到/api/auth/login
以获得token会返回以下数据:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
"token_type": "bearer",
"expires_in": 3600
}
在实际使用时可以将返回的access_token保存到浏览器的LocalStorage
或Cookie中等等都行,然后在每次发送需要通过认证的ajax请求时在Header中附带以下参数即可:
Authorization: Bearer eyJhbGciOiJIUzI1NiI...
若认证失败服务端返回的json数据将会是401 Unauthorized
Laravel + Vue + axios 最佳实践
在用户未认证的情况需要在Web中填写登陆表单时,我在登陆页编写了如下js
//首先获取Web中的csrf token以发送http post请求
axios.defaults.headers.common = {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
"X-Requested-With": "XMLHttpRequest"
}
//点击登陆按钮后首先发送api的ajax login请求判断登陆凭据是否正确,若正确则保存返回的token到localStorage中供下次使用
handleLogin = function() {
var loginForm = document.getElementById("login-form")
var email = document.getElementById("email").value
var password = document.getElementById("password").value
axios
.post("/api/auth/login", {
email: email,
password: password
})
.then(response => {
localStorage.setItem("token", response.data.access_token)
loginForm.submit()
})
.catch(error => {
loginForm.submit()
})
}
登陆成功后跳转到使用Vue
编写的一个Web App,在初始化Vue组件前,获取之前在登陆时保存到localStorage的token,添加到axios的公共头部中,以便让后面的所有请求都带上这个token,那么接下来在使用Vue的整个生命周期中发送axios的api请求便都不用在意token如何附带进去了
const app = new Vue({
el: '#app',
beforeCreate:function () {
//let that = this;
if (localStorage.token) {
axios.defaults.headers.common['Authorization'] = 'Bearer' + localStorage.token;
}
},
render: h => h(App)
});