Laravel 5 中使用 JWT(Json Web Token) 實現基於API的用戶認證


Laravel使用JWT實現API認證

在JavaScript前端技術大行其道的今天,我們通常只需在后台構建API提供給前端調用,並且后端僅僅設計為給前端移動App調用。用戶認證是Web應用的重要組成部分,基於API的用戶認證有兩個最佳解決方案 —— OAuth 2.0 和 JWT(JSON Web Token)。

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編碼可以得到下面的字符串:

eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ

這個字符串我們將它稱作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

安裝

我們使用Composer安裝jwt擴展包:

composer require tymon/jwt-auth 0.5.*

配置

安裝完成后,需要在config/app.php中注冊相應的服務提供者:

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

然后注冊需要用到的對應門面:

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class

然后發布相應配置文件:

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

最后生成密鑰:

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直到它們失效

創建Token

創建用戶token最常用的方式就是通過登錄實現用戶認證,如果成功則返回相應用戶的token。這里假設我們有一個AuthenticateController

use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthenticateController extends Controller
{
    public function authenticate(Request $request)
    {
        // grab credentials from the request
        $credentials = $request->only('email', 'password');

        try {
            // attempt to verify the credentials and create a token for the user
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong whilst attempting to encode the token
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        // all good so return the token
        return response()->json(compact('token'));
    }
}

有時候我們還可以直接通過用戶對象實例創建token:

// grab some user
$user = User::first();
$token = JWTAuth::fromUser($user);

此外,還可以使用Tymon\JWTAuth\PayloadFactory實例(或者JWTFactory門面)基於任意數據創建token:

$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);

還可以使用方法鏈的方式:

// add a custom claim with a key of `foo` and a value of ['bar' => 'baz']
$payload = JWTFactory::sub(123)->aud('foo')->foo(['bar' => 'baz'])->make();
$token = JWTAuth::encode($payload);

用戶認證

用戶登錄成功之后,下一步就是發送一個包含token的請求來獲取用戶信息。

要通過http發送一個需要認證通過的請求,需要設置Authorization頭:

Authorization: Bearer {yourtokenhere}

如果用戶名/密碼沒有進行base64編碼那么Apache似乎會摒棄Authorization頭,要修復這一問題你可以添加如下代碼到Apache配置文件:

RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

或者將token信息包含到URL中:

http://api.mysite.com/me?token={yourtokenhere}

要從請求中獲取token,你可以這么做:

// this will set the token on the object
JWTAuth::parseToken();// and you can continue to chain methods
$user = JWTAuth::parseToken()->authenticate();

要獲取該token值,你可以這么調用:

$token = JWTAuth::getToken();

如果token被設置則會返回,否則會嘗試使用方法從請求中解析token,如果token未被設置或不能解析最終返回false。

當然如果需要的話你還可以手動設置token:

JWTAuth::setToken('foo.bar.baz');

從Token中獲取認證用戶:

// somewhere in your controller
public function getAuthenticatedUser()
{
    try {
        if (! $user = JWTAuth::parseToken()->authenticate()) {
            return response()->json(['user_not_found'], 404);
        }
    } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
        return response()->json(['token_expired'], $e->getStatusCode());
    } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
        return response()->json(['token_invalid'], $e->getStatusCode());
    } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
        return response()->json(['token_absent'], $e->getStatusCode());
    }

    // the token is valid and we have found the user via the sub claim
    return response()->json(compact('user'));
}

jwt-auth擴展還提供了兩個中間件GetUserFromTokenRefreshToken,前者用於在請求頭和參數中檢查是否包含token,並嘗試對其解碼,后者會再次從請求中解析token,並順序刷新token(同時廢棄老的token)並將其作為下一個響應的一部分。要使用這兩個中間件,需要到app/Http/Kernel.php下的$routeMiddleware屬性中注冊它們:

protected $routeMiddleware = [
    ...
    'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
    'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',
];

JWT讓用戶認證變得簡單和安全,token會被保存到本地的storage/web或Cookie中,使用JWT,基於API的用戶認證將不再困難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM