在 lumen 中使用 jwt(译)

如何在 lumen 中使用 jwt (json web token)

##安装 jwt

在你的项目根目录执行下面的命令

1
composer require tymon/jwt-auth

然后我们必须在 Provider 中添加 JWTAuthServiceProvider。 在 Laravel 中有一个名为 config 的文件夹,其中有一个配置文件 app.php,一般都在这个文件中添加 Service Provider,但是在 lumen 中有一些不同。

打开 bootstrap/app.php 文件,找到 Register Service Provider。在这里添加 JWTAuthServiceProvider。

重要提示:如果此时使用 php artisan 命令,你会得到一个config_path 不存在的错误。

改进 lumen

Lumen 默认是没有 config_path 功能的,因为 Lumen 为了文件简洁,将所有配置选项存储在 .env 中,Lumen 中也没有 config 这个文件夹。

这个问题可以很容易的解决掉,手动创建一个 config_path 功能,在 app 目录下创建一个文件 helpers.php。你可以找到这个功能在 GitHub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

if ( ! function_exists('config_path'))
{
/**
* Get the configuration path.
*
* @param string $path
* @return string
*/
function config_path($path = '')
{
return app()->basePath() . '/config' . ($path ? '/' . $path : $path);
}
}

现在我们必须让 composer 自动加载这个文件。打开 composer.json 添加下面的内容:

1
2
3
4
5
6
"autoload": {
...
"files": [
"app/helpers.php"
]
},

运行 composer dump-autoload ,然后继续。

现在我们已经解决了之前的那个错误,我们 现在需要发布这个包的配置文件。

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

此时,你会看到另外一个错误提示 There are no commands defined in the “vendor” namespace.

这是因为 Lumen 默认没有 vender:publish 命令。我创建了一个扩展包,可以解决这个问题。安装扩展包: VendorPublishCommand

按照这个扩展包的描述文件,添加 vendor:pulish 命令到 Lumen 中。完成之后重新运行命令:

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

要完成所有的安装,这里还有一个可选的步骤:adding facades。

Adding facades

我发现在 Lumen 中设置一个 facade 最简单的方法是将其添加到 bootstrap/app.php 中。

为了能够使用 JWT 配置文件,我们必须添加:

1
$app->configure('jwt');

在 bootstrap/app.php 中声明,在 $app->withFacades() 的下面。

现在我们添加这个 facades:

1
2
3
4
5
class_alias('Tymon\JWTAuth\Facades\JWTAuth', 'JWTAuth');
/** This gives you finer control over the payloads you create if you require it.
* Source: https://github.com/tymondesigns/jwt-auth/wiki/Installation
*/
class_alias('Tymon\JWTAuth\Facades\JWTFactory', 'JWTFactory'); // Optional

继续安装过程,最后一步是生成一个密匙。这个命令可以生成一个鄙视并保存到配置文件中。

1
php artisan jwt:generate

至此完成 facade 的添加。

配置

在这一部分,你要检查配置文件,并且根据你的需要修改配置文件。打开 config/jwt.php,亦可以看到之前生成的密匙。你可以移动这个密匙到 .env 中。我没有在这里做任何改变,如果你想理解这些配置文件可以查看这个文档

一个使用 Postman 测试 API 的提示。当你使用 PUT,PATCH 方法时,确认使用了 x-www-form-urlencoded 选项,而不是默认的 form-data。

认证

我们希望能够指定哪些路由需要认证,为了做到这一点,我们需要注册 JWT 中间件。

打开 bootstrap/app.php ,找到 Register Middleware 部分,取消 routeMiddleware 的注释,并添加以下内容:

1
2
3
4
$app->routeMiddleware([
'jwt.auth' => Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.refresh' => Tymon\JWTAuth\Middleware\RefreshToken::class,
]);

一旦中间件在引导文件中定义,你可以在路由 routes.php 中使用 middleware 关键词。只有 store,update 和 destory 路由要求认证,所以像下面这样修改路由。

1
2
3
4
5
6
7
8
9
10
11
$app->group(['prefix' => 'projects', 'middleware' => 'jwt.auth'], function($app) {
$app->post('/', 'App\Http\Controllers\ProjectsController@store');
$app->put('/{projectId}', 'App\Http\Controllers\ProjectsController@update');
$app->delete('/{projectId}', 'App\Http\Controllers\ProjectsController@destroy');
});

$app->group(['prefix' => 'projects'], function ($app)
{
$app->get('/', 'App\Http\Controllers\ProjectsController@index');
$app->get('/{projectId}', 'App\Http\Controllers\ProjectsController@show');
});

如果你试图触发热和使用 jwt.auth 中间件的路由,你会得到下面的错误:

1
Class Illuminate\Routing\ResponseFactory does not exist

解决这个问题,我们需要运行一下命令,添加 illuminate/routing 包。

1
composer require illuminate/routing

好了,现在当我们试图发送一个 POST 请求时,我们会得到这样的响应

1
2
3
{
"error": "token_invalid"
}

这意味这我们必须登录(获得 token)后才能进行这个操作。这也意味着认证已经起作用了。现在让我们获得令牌。

获得令牌(登录)

我们在对 API 执行任何 POST,PUT,DELETE时,必须先登录,也就是说我们需要在每一次请求中带上一个 token。

据我所知 Lumen 并没有开箱即用的验证,所以我们需要创建一个 auth/login 路由接收 email 和 密码,并在登录成功后返回 JWT token。

首先,我们创建一个登录的路由:

1
$app->post('auth/login', 'App\Http\Controllers\Auth\AuthController@postLogin');

现在我们创建控制器在app/Http/Controllers/Auth/AuthController.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Exception\HttpResponseException;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response as IlluminateResponse;

class AuthController extends Controller {

/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postLogin(Request $request)
{
try
{
$this->validate($request, [
'email' => 'required|email|max:255', 'password' => 'required',
]);
}
catch (HttpResponseException $e)
{
return response()->json([
'error' => [
'message' => 'Invalid auth',
'status_code' => IlluminateResponse::HTTP_BAD_REQUEST
]],
IlluminateResponse::HTTP_BAD_REQUEST,
$headers = []
);
}

$credentials = $this->getCredentials($request);

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'));
}

/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function getCredentials(Request $request)
{
return $request->only('email', 'password');
}
}

我使用 php artisan tinker 创建了一个虚拟用户,你可以使用任意的办法在数据库中创建一个用户。

现在,当你发送一个包含 email 和 password 的请求到 auth/login,你将获得一个像这样的响应:

1
2
3
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6XC9cL2x1bWVuLnRyZWVsaW5lLmFwcFwvYXV0aFwvbG9naW4iLCJpYXQiOiIxNDM1MjI4MDk2IiwiZXhwIjoiMTQzNTIzMTY5NiIsIm5iZiI6IjE0MzUyMjgwOTYiLCJqdGkiOiJmZjJhOTZkYjI4YTY2MzY5ZmNmZjZjNDEyYWRkNjdiMyJ9.M2NjNzY3NGU4NGM1MGI2ZDA4MTlhZmE5MGYwNjE5MzNmNGFlNmEyZTJmNTgwMjQ4MjUzMzAyZjQxMmI1MjViNQ"
}

你要记得收到的这个 token,因为我们需要使用它构造请求。

访问受保护的路由(使用 token)

为了能够访问 API 中要求验证的部分,你需要像下面这样设置一个认证头:

1
Authorization: Bearer {yourtokenhere}

现在,在你需要验证的方法中,你可以使用下面的方法:

1
$user = JWTAuth::parseToken()->authenticate();

参考文章:http://laravelista.com/json-web-token-authentication-for-lumen/