[关闭]
@Chiang 2019-12-29T18:34:10.000000Z 字数 9237 阅读 599

Laravel 中使用 JWT 认证的 Restful API

JWT Laravel Restful


  • 在此文章中,我们将学习如何使用 JWT 身份验证在 Laravel 中构建 restful API 。 JWT 代表 JSON Web Tokens 。 我们还将使用 API 为用户产品创建功能齐全的 CRUD 应用。
  • 在使用跨平台应用程序时, API 是一个非常不错的选择。 除了网站,您的产品可能还有 Android 和 iOS 应用程序。 在这种情况下, API 也是同样出色的,因为您可以在不更改任何后端代码的情况下编写不同的前端。 使用 API 时,只需使用一些参数点击 GET , POST 或其他类型的请求,服务器就会返回 JSON (JavaScript Object Notation) 格式的一些数据,这些数据由客户端应用程序处理。

说明

我们先写下我们的应用程序详细信息和功能。 我们将使用 JWT 身份验证在 laravel 中使用 restful API 构建基本用户产品列表。

User 将会使用以下功能

  • 注册并创建一个新帐户
  • 登录到他们的帐户
  • 注销和丢弃 token 并离开应用程序
  • 获取登录用户的详细信息
  • 检索可供用户使用的产品列表
  • 按 ID 查找特定产品
  • 将新产品添加到用户产品列表中
  • 编辑现有产品详细信息
  • 从用户列表中删除现有产品

User 必填

  • name
  • email
  • password

Product 必填

  • name
  • price
  • quantity

创建新的项目

通过运行下面的命令,我们就可以开始并创建新的 Laravel 项目。

  1. composer create-project --prefer-dist laravel/laravel jwt

这会在名为 jwt 的目录下创建一个新的 Laravel 项目。

配置JWT扩展包

我们会使用 tymondesigns/jwt-auth 扩展包来让我们在 Laravel 中使用 JWT。

安装 tymon/jwt-auth 扩展包

让我们在这个 Laravel 应用中安装这个扩展包。如果您正在使用 Laravel 5.5 或以上版本,请运行以下命令来获取 dev-develop 版本的 JWT 包:

  1. composer require tymon/jwt-auth:dev-develop --prefer-source

发布配置文件

对于 5.5 或以上版本 的 Laravel,请使用下面这条命令来发布配置文件:

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

上面的命令会生成 config/jwt.php 配置文件。除去注释部分,配置文件会像这样:

  1. <?php
  2. return [
  3. 'secret' => env('JWT_SECRET'),
  4. 'keys' => [
  5. 'public' => env('JWT_PUBLIC_KEY'),
  6. 'private' => env('JWT_PRIVATE_KEY'),
  7. 'passphrase' => env('JWT_PASSPHRASE'),
  8. ],
  9. 'ttl' => env('JWT_TTL', 60),
  10. 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
  11. 'algo' => env('JWT_ALGO', 'HS256'),
  12. 'required_claims' => [
  13. 'iss',
  14. 'iat',
  15. 'exp',
  16. 'nbf',
  17. 'sub',
  18. 'jti',
  19. ],
  20. 'persistent_claims' => [
  21. // 'foo',
  22. // 'bar',
  23. ],
  24. 'lock_subject' => true,
  25. 'leeway' => env('JWT_LEEWAY', 0),
  26. 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
  27. 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
  28. 'decrypt_cookies' => false,
  29. 'providers' => [
  30. 'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
  31. 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
  32. 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
  33. ],
  34. ];

生成JWT密钥

JWT 令牌通过一个加密的密钥来签发。对于 Laravel 5.5 或以上版本,运行下面的命令来生成密钥以便用于签发令牌。

  1. php artisan jwt:secret

注册中间件

JWT 认证扩展包附带了允许我们使用的中间件。在 app/Http/Kernel.php 中注册 auth.jwt 中间件:

  1. protected $routeMiddleware = [
  2. ....
  3. 'auth.jwt' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class,
  4. ];

这个中间件会通过检查请求中附带的令牌来校验用户的认证。如果用户未认证,这个中间件会抛出 UnauthorizedHttpException 异常。

设置路由

开始之前,我们将为所有本教程讨论的点设置路由。打开 routes/api.php 并将下面的路由复制到您的文件中。

  1. Route::post('login', 'ApiController@login');
  2. Route::post('register', 'ApiController@register');
  3. Route::group(['middleware' => 'auth.jwt'], function () {
  4. Route::get('logout', 'ApiController@logout');
  5. Route::get('user', 'ApiController@getAuthUser');
  6. Route::get('products', 'ProductController@index');
  7. Route::get('products/{id}', 'ProductController@show');
  8. Route::post('products', 'ProductController@store');
  9. Route::put('products/{id}', 'ProductController@update');
  10. Route::delete('products/{id}', 'ProductController@destroy');
  11. });

更新User模型

JWT 需要在 User 模型中实现 Tymon\JWTAuth\Contracts\JWTSubject 接口。 此接口需要实现两个方法 getJWTIdentifier 和 getJWTCustomClaims。使用以下内容更新 app/User.php 。

  1. <?php
  2. namespace App;
  3. use Illuminate\Foundation\Auth\User as Authenticatable;
  4. use Illuminate\Notifications\Notifiable;
  5. use Tymon\JWTAuth\Contracts\JWTSubject;
  6. class User extends Authenticatable implements JWTSubject
  7. {
  8. use Notifiable;
  9. /**
  10. * The attributes that are mass assignable.
  11. *
  12. * @var array
  13. */
  14. protected $fillable = [
  15. 'name', 'email', 'password',
  16. ];
  17. /**
  18. * The attributes that should be hidden for arrays.
  19. *
  20. * @var array
  21. */
  22. protected $hidden = [
  23. 'password', 'remember_token',
  24. ];
  25. /**
  26. * Get the identifier that will be stored in the subject claim of the JWT.
  27. *
  28. * @return mixed
  29. */
  30. public function getJWTIdentifier()
  31. {
  32. return $this->getKey();
  33. }
  34. /**
  35. * Return a key value array, containing any custom claims to be added to the JWT.
  36. *
  37. * @return array
  38. */
  39. public function getJWTCustomClaims()
  40. {
  41. return [];
  42. }
  43. }

JWT身份验证逻辑

让我们使用 JWT 身份验证在 laravel 中写 Restful API 的逻辑。

用户注册时需要姓名,邮箱和密码。那么,让我们创建一个表单请求来验证数据。通过运行以下命令创建名为 RegisterAuthRequest 的表单请求:

  1. php artisan make:request RegisterAuthRequest

它将在 app/Http/Requests 目录下创建 RegisterAuthRequest.php 文件。将下面的代码黏贴至该文件中。

  1. <?php
  2. namespace App\Http\Requests;
  3. use Illuminate\Foundation\Http\FormRequest;
  4. class RegisterAuthRequest extends FormRequest
  5. {
  6. /**
  7. * 确定是否授权用户发出此请求
  8. *
  9. * @return bool
  10. */
  11. public function authorize()
  12. {
  13. return true;
  14. }
  15. /**
  16. * 获取应用于请求的验证规则
  17. *
  18. * @return array
  19. */
  20. public function rules()
  21. {
  22. return [
  23. 'name' => 'required|string',
  24. 'email' => 'required|email|unique:users',
  25. 'password' => 'required|string|min:6|max:10'
  26. ];
  27. }
  28. }

运行以下命令创建一个新的 ApiController :

  1. php artisan make:controller ApiController

这将会在 app/Http/Controllers 目录下创建 ApiController.php 文件。将下面的代码黏贴至该文件中。

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Requests\RegisterAuthRequest;
  4. use App\User;
  5. use Illuminate\Http\Request;
  6. use JWTAuth;
  7. use Tymon\JWTAuth\Exceptions\JWTException;
  8. class ApiController extends Controller
  9. {
  10. public $loginAfterSignUp = true;
  11. public function register(RegisterAuthRequest $request)
  12. {
  13. $user = new User();
  14. $user->name = $request->name;
  15. $user->email = $request->email;
  16. $user->password = bcrypt($request->password);
  17. $user->save();
  18. if ($this->loginAfterSignUp) {
  19. return $this->login($request);
  20. }
  21. return response()->json([
  22. 'success' => true,
  23. 'data' => $user
  24. ], 200);
  25. }
  26. public function login(Request $request)
  27. {
  28. $input = $request->only('email', 'password');
  29. $jwt_token = null;
  30. if (!$jwt_token = JWTAuth::attempt($input)) {
  31. return response()->json([
  32. 'success' => false,
  33. 'message' => 'Invalid Email or Password',
  34. ], 401);
  35. }
  36. return response()->json([
  37. 'success' => true,
  38. 'token' => $jwt_token,
  39. ]);
  40. }
  41. public function logout(Request $request)
  42. {
  43. $this->validate($request, [
  44. 'token' => 'required'
  45. ]);
  46. try {
  47. JWTAuth::invalidate($request->token);
  48. return response()->json([
  49. 'success' => true,
  50. 'message' => 'User logged out successfully'
  51. ]);
  52. } catch (JWTException $exception) {
  53. return response()->json([
  54. 'success' => false,
  55. 'message' => 'Sorry, the user cannot be logged out'
  56. ], 500);
  57. }
  58. }
  59. public function getAuthUser(Request $request)
  60. {
  61. $this->validate($request, [
  62. 'token' => 'required'
  63. ]);
  64. $user = JWTAuth::authenticate($request->token);
  65. return response()->json(['user' => $user]);
  66. }
  67. }

让我解释下上面的代码发生了什么。

在 register 方法中,我们接收了 RegisterAuthRequest 。使用请求中的数据创建用户。如果 loginAfterSignUp 属性为 true ,则注册后通过调用 login 方法为用户登录。否则,成功的响应则将伴随用户数据一起返回。

在 login 方法中,我们得到了请求的子集,其中只包含电子邮件和密码。以输入的值作为参数调用 JWTAuth::attempt() ,响应保存在一个变量中。如果从 attempt 方法中返回 false ,则返回一个失败响应。否则,将返回一个成功的响应。

在 logout 方法中,验证请求是否包含令牌验证。通过调用 invalidate 方法使令牌无效,并返回一个成功的响应。如果捕获到 JWTException 异常,则返回一个失败的响应。

在 getAuthUser 方法中,验证请求是否包含令牌字段。然后调用 authenticate 方法,该方法返回经过身份验证的用户。最后,返回带有用户的响应。

身份验证部分现在已经完成。

构建产品部分

要创建产品部分,我们需要 Product 模型,控制器和迁移文件。运行以下命令来创建 Product 模型,控制器和迁移文件。

  1. php artisan make:model Product -mc

它会在 database/migrations 目录下创建一个新的数据库迁移文件 create_products_table.php,更改 up 方法。

  1. public function up()
  2. {
  3. Schema::create('products', function (Blueprint $table) {
  4. $table->increments('id');
  5. $table->integer('user_id');
  6. $table->string('name');
  7. $table->integer('price');
  8. $table->integer('quantity');
  9. $table->timestamps();
  10. $table->foreign('user_id')
  11. ->references('id')
  12. ->on('users')
  13. ->onDelete('cascade');
  14. });
  15. }

向 Product 模型中添加 fillable 属性。在 app 目录下打开 Product.php 文件并添加属性。

  1. protected $fillable = [
  2. 'name', 'price', 'quantity'
  3. ];

现在在 .env 文件中设置数据库凭证,并通过运行以下命令迁移数据库。

  1. php artisan migrate

现在,我们必须在 User 模型中添加一个关系来检索相关产品。在 app/User.php 中添加以下方法。

  1. public function products()
  2. {
  3. return $this->hasMany(Product::class);
  4. }

在 app/Http/Controllers 目录下打开 ProductController.php 文件。在文件开头添加 use 指令覆盖上一个。

  1. use App\Product;
  2. use Illuminate\Http\Request;
  3. use JWTAuth;

现在我们将实现五个方法。

  • index, 为经过身份认证的用户获取所有产品列表
  • show, 根据 ID 获取特定的产品
  • store, 将新产品存储到产品列表中
  • update, 根据 ID 更新产品详情
  • destroy, 根据 ID 从列表中删除产品

添加一个构造函数来获取经过身份认证的用户,并将其保存在 user 属性中。

  1. protected $user;
  2. public function __construct()
  3. {
  4. $this->user = JWTAuth::parseToken()->authenticate();
  5. }

parseToken 将解析来自请求的令牌, authenticate 通过令牌对用户进行身份验证。

让我们添加 index 方法.

  1. public function index()
  2. {
  3. return $this->user
  4. ->products()
  5. ->get(['name', 'price', 'quantity'])
  6. ->toArray();
  7. }

上面的代码非常简单,我们只是使用 Eloquent 的方法获取所有的产品,然后将结果组成一个数组。最后,我们返回这个数组。Laravel 将自动将其转换为 JSON ,并创建一个为 200 成功的响应码。

继续实现 show 方法。

  1. public function show($id)
  2. {
  3. $product = $this->user->products()->find($id);
  4. if (!$product) {
  5. return response()->json([
  6. 'success' => false,
  7. 'message' => 'Sorry, product with id ' . $id . ' cannot be found'
  8. ], 400);
  9. }
  10. return $product;
  11. }

这个也非常容易理解。我们只需要根据 ID 找到该产品。如果产品不存在,则返回 400 故障响应。否则,将返回产品数组。

接下来是 store 方法

  1. public function store(Request $request)
  2. {
  3. $this->validate($request, [
  4. 'name' => 'required',
  5. 'price' => 'required|integer',
  6. 'quantity' => 'required|integer'
  7. ]);
  8. $product = new Product();
  9. $product->name = $request->name;
  10. $product->price = $request->price;
  11. $product->quantity = $request->quantity;
  12. if ($this->user->products()->save($product))
  13. return response()->json([
  14. 'success' => true,
  15. 'product' => $product
  16. ]);
  17. else
  18. return response()->json([
  19. 'success' => false,
  20. 'message' => 'Sorry, product could not be added'
  21. ], 500);
  22. }

在 store 方法中,验证请求中是否包含名称,价格和数量。然后,使用请求中的数据去创建一个新的产品模型。如果,产品成功的写入数据库,会返回成功响应,否则返回自定义的 500 失败响应。

实现 update 方法

  1. public function update(Request $request, $id)
  2. {
  3. $product = $this->user->products()->find($id);
  4. if (!$product) {
  5. return response()->json([
  6. 'success' => false,
  7. 'message' => 'Sorry, product with id ' . $id . ' cannot be found'
  8. ], 400);
  9. }
  10. $updated = $product->fill($request->all())
  11. ->save();
  12. if ($updated) {
  13. return response()->json([
  14. 'success' => true
  15. ]);
  16. } else {
  17. return response()->json([
  18. 'success' => false,
  19. 'message' => 'Sorry, product could not be updated'
  20. ], 500);
  21. }
  22. }

在 update 方法中,我们通过 id 取得产品。如果产品不存在,返回一个 400 响应。然后,我们把请求中的数据使用 fill 方法填充到产品详情。更新产品模型并保存到数据库,如果记录成功更新,返回一个 200 成功响应,否则返回 500 内部服务器错误响应给客户端。

现在,让我们实现 destroy 方法。

  1. public function destroy($id)
  2. {
  3. $product = $this->user->products()->find($id);
  4. if (!$product) {
  5. return response()->json([
  6. 'success' => false,
  7. 'message' => 'Sorry, product with id ' . $id . ' cannot be found'
  8. ], 400);
  9. }
  10. if ($product->delete()) {
  11. return response()->json([
  12. 'success' => true
  13. ]);
  14. } else {
  15. return response()->json([
  16. 'success' => false,
  17. 'message' => 'Product could not be deleted'
  18. ], 500);
  19. }
  20. }

在 destroy 方法中,我们根据 ID 获取产品,如果产品不存在,则返回 400 响应。然后我们删除产品后并根据删除操作的成功状态返回适当的响应。

控制器代码现在已经完成,完整的控制器代码在这。


参考资料:
Laravel 中使用 JWT 认证的 Restful API

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注