laravel5.4 JWT 使用教程

Json web token

什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT代表Json Web Token.JWT能有效地进行身份验证并连接前后端。

1、JWT定义及其组成

JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

载荷(Payload)

我们先将用户认证的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT。\

{
    "sub": "1",
    "iss": "http://localhost:8000/auth/login",
    "iat": 1451888119,
    "exp": 1454516119,
    "nbf": 1451888119,
    "jti": "37c107e4609ddbcc9c096ea5ee76c667"
}

这里面的前6个字段都是由JWT的标准所定义的。

  • sub: 该JWT所面向的用户
  • iss: 该JWT的签发者
  • iat(issued at): 在什么时候签发的token
  • exp(expires): token什么时候过期
  • nbf(not before):token在此时间之前不能被接收处理
  • jti:JWT ID为web token提供唯一标识

这些定义都可以在标准中找到。

将上面的JSON对象进行base64编码可以得到下面的字符串:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuMTUzOjgwMDIvYXBpL2xvZ2luIiwiaWF0IjoxNTA1MjkyMzk5LCJleHAiOjE1MDUyOTU5OTksIm5iZiI6MTUwNTI5MjM5OSwianRpIjoiU3JCOWVISDZTSzBoUVlkcyJ9.8xqHrWMyk9oEMAQ-DLHZDyNlm6N-nJWEjtG7-4aX-F0

这个字符串我们将它称作JWT的Payload(载荷)。

如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串:

var base64url = require('base64url')
var header = {
    "from_user": "B",
    "target_user": "A"
}
console.log(base64url(JSON.stringify(header)))

注:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

头部(Header)

JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象:

{
  "typ": "JWT",
  "alg": "HS256"
}

在这里,我们说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法。

对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

签名(签名)

将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ

最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret):

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)

这样就可以得到我们加密后的内容:

wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4

这一部分又叫做签名。

最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ.wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4

2、集成JWT到Laravel 5.4

安装

我们使用Composer安装jwt扩展包:

composer require tymon/jwt-auth

配置

安装完成后,需要在  config/app.php添加service provider 和 aliase

'providers' => [
    ....
    Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class, 
],
'aliases' => [
    ....
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class
],

然后发布相应配置文件,比如令牌到期时间配置等,再config/jwt.php中配置:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

最后一步需要生成JWT Key:

php artisan jwt:generate

如果你想要将其添加到.env文件中,在.env中创建JWT_SECRET字段并再次执行生成密钥的命令。

在config/jwt.php中,你可以配置以下选项:

  • ttl:token有效期(分钟)
  • refresh_ttl:刷新token时间(分钟)
  • algo:token签名算法
  • user:指向User模型的命名空间路径
  • identifier:用于从token的sub中获取用户
  • require_claims:必须出现在token的payload中的选项,否则会抛出TokenInvalidException异常
  • blacklist_enabled:如果该选项被设置为false,那么我们将不能废止token,即使我们刷新了token,前一个token仍然有效
  • providers:完成各种任务的具体实现,如果需要的话你可以重写他们
    • User —— providers.user:基于sub获取用户的实现
    • JWT —— providers.jwt:加密/解密token
    • Authentication —— providers.auth:通过证书/ID获取认证用户
    • Storage —— providers.storage:存储token直到它们失效

创建API路由

我在创建Api路由的时候会用到一个“cors”中间件,虽然它不是强制性的,但是后面你会发现报类似这样的错:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://xxx.com/api/register. (Reason: CORS header 'Access-Control-Allow-Origin' missing)

大致翻译下,“跨源请求阻塞:同源策略不允许读取http://kylesean.com/api/register远程资源。(原因:CORS 头“Access-Control-Allow-Origin” 没有)。” 这就是跨域请求导致的错误消息,当然你可以自定义Header,Origin, Method来解决跨域问题,不过我这边推荐一个package:barryvdh/laravel-cors,这里安装它:

composer require barryvdh/laravel-cors

安装成功后,创建中间件:

php artisan make:middleware CORS

进入app/Http/Middleware,编辑CORS.php:

namespace App\Http\Middleware;
use Closure;
class CORS
{
    public function handle($request, Closure $next)
    {
        header('Access-Control-Allow-Origin: *');

        $headers = [
            'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
            'Access-Control-Allow-Headers'=> 'Content-Type, X-Auth-Token, Origin'
        ];
        if($request->getMethod() == "OPTIONS") {
            return Response::make('OK', 200, $headers);
        }

        $response = $next($request);
        foreach($headers as $key => $value)
            $response->header($key, $value);
        return $response;
    }
}

在app/Http/Kernel.php注册中间件:

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    ...
    ...
    protected $routeMiddleware = [
        ...
        'cors' => \App\Http\Middleware\CORS::class,
    ];
}

有了这个中间件我们就解决了跨域问题。接下来回到路由,app/Http/routes.php:

Route::group(['middleware' => ['api','cors'],'prefix' => 'api'], function () {
    Route::post('register', 'ApiController@register');     // 注册
    Route::post('login', 'ApiController@login');           // 登陆
    Route::group(['middleware' => 'jwt.auth'], function () {
        Route::post('get_user_details', 'APIController@get_user_details');  // 获取用户详情
    });
});

上面的jwt-auth中间件现在还是无效的,接着创建这个middleware:

php artisan make:middleware authJWT

同样的我们需要编辑下这个authJWT.php:

namespace App\Http\Middleware;
use Closure;
use Tymon\JWTAuth\Facades\JWTAuth;
use Exception;
class authJWT
{
    public function handle($request, Closure $next)
    {
        try {
            // 如果用户登陆后的所有请求没有jwt的token抛出异常
            $user = JWTAuth::toUser($request->input('token')); 
        } catch (Exception $e) {
            if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){
                return response()->json(['error'=>'Token 无效']);
            }else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){
                return response()->json(['error'=>'Token 已过期']);
            }else{
                return response()->json(['error'=>'出错了']);
            }
        }
        return $next($request);
    }
}

接着注册该中间件在app/Http/Kernel.php:

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    ...
    ...
    protected $routeMiddleware = [
        ...
        'jwt.auth' => \App\Http\Middleware\authJWT::class,
    ];
}

创建控制器管理所有的请求 app/Http/Controllers/ApiController.php:

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use Illuminate\Support\Facades\Hash;
use Tymon\JWTAuth\Facades\JWTAuth;

class ApiController extends Controller
{
    /*注册*/
    public function register(Request $request)
    {
        $input = $request->all();
        $input['password'] = Hash::make($input['password']);
        User::create($input);
        return response()->json(['result'=>true]);
    }

    /*登陆*/
    public function login(Request $request)
    {
        $input = $request->all();
        if (!$token = JWTAuth::attempt($input)) {
            return response()->json(['result' => '邮箱或密码错误.']);
        }
        return response()->json(['result' => $token]);
    }

    /*获取用户信息*/
    public function get_user_details(Request $request)
    {
        $input = $request->all();
        $user = JWTAuth::toUser($input['token']);
        return response()->json(['result' => $user]);
    }

}

建议:过滤掉路由api/*下的csrf_token,方便测试开发

在App/Http/Middleware/VerifyCsrfToken.php

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;

class VerifyCsrfToken extends BaseVerifier
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        "api/*"
    ];
}

最后通过 postman 进行测试:

1、先注册一个账户,POST请求:127.0.0.1/api/regster

这里是我注册的例子,成功返回true:

2、用户登陆:

3、获取用户信息:

 

 https://laravel-china.org/topics/2811/laravel-52-uses-jwt-to-complete-multi-user-authentication

 

posted @ 2017-09-13 17:27  丶老中医  阅读(1188)  评论(0编辑  收藏  举报
一切已经开始©2018 丶老中医