Laravel + JWT 实现 API 跨域授权
简介配置 Laravel 5.6 + JWT + AngularJS 配置 Api 跨域授权访问,以及实现 Token 刷新。
因内容较多,涉及较广,这里只能概括的讲一下了。
安装配置
JWT
详细配置过程,请查看官方文档。
- 安装 tymondesigns/jwt-auth
composer require tymon/jwt-auth "1.0.0-rc.2"
- 生成key
php artisan jwt:secret
- User 实现 JWTSubject 接口
- 修改 auth.php 配置
'api' => [ 'driver' => 'jwt', 'provider' => 'users', ],
-
一个【授权 + 刷新】 的中间件
添加到app/Http/Kernel.php
protected $routeMiddleware = [ ... 'refresh.token' => \App\Http\Middleware\RefreshToken::class ... ];
RefreshToken 中间件
class RefreshToken extends BaseMiddleware { public function handle($request, Closure $next) { // 检查此次请求中是否带有 token,如果没有则抛出异常。 $this->checkForToken($request); Log::debug($request->headers->all()); // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常 try { // 检测用户的登录状态,如果正常则通过 if ($this->auth->parseToken()->authenticate()) { return $next($request); } $token = $this->auth->refresh(); // 使用一次性登录以保证此次请求的成功 } catch (TokenExpiredException $exception) { // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中 try { // 刷新用户的 token $token = $this->auth->refresh(); // 使用一次性登录以保证此次请求的成功 Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']); } catch (JWTException $exception) { // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。 throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage()); } } // 在响应头中返回新的 token return $this->setAuthenticationHeader($next($request), $token); } }
- 几个关键的配置
- JWT_TTL Token的有效时间,单位:分,建议设置 20 min ~ 1 h。
- JWT_REFRESH_TTL=1440 刷新后的 Token 有效时间,单位:分,建议设置在 7 ~ 30 day。
- JWT_BLACKLIST_GRACE_PERIOD 防止并发请求导致某些请求失效,单位:秒,建议设置 10 ~ 60 s。
跨域
详细配置过程,请查看官方文档。
- 安装 barryvdh/laravel-cors
- 配置 cors
在配置文件 cors.php 中,设置exposedHeaders
,不然无法跨域获取到 header 中的 Authorization 值来刷新前端令牌。'exposedHeaders' => ['Authorization'],
后端 Laravel
登录
-
登录 Api 接口
验证账号密码,返回 Token,我这里使用的 username 字段。
Api返回,自己找一个返回success、error、message 的 Trait 工具就可以了,没必要用 dingo。class LoginController extends ApiController { use AuthenticatesUsers; public function login(LoginRequest $request) { $credentials = $this->credentials($request); if ($token = auth('api')->attempt($credentials)) { $user = auth('api')->user(); return $this->success( [ 'user' => $user, 'token' => $token, ]); } return $this->failed('账号或密码错误!'); } public function username() { return 'username'; } }
- 路由
Api 接口都要使用 api 中间件,控制器最好放在 \Api 命名空间下。Route::middleware('api')->prefix('api/user')->namespace('Modules\Account\Http\Controllers\Api')->group(function () { Route::post('login', 'LoginController@login')->name('user.login'); });
- 给需要授权的接口添加中间件,比如获取用户列表 getList
public function __construct(UserRepository $repository) { $this->middleware('refresh.token')->only([ 'getList', 'get', 'update' ]); $this->repository = $repository; }
前端 Angular
Token
在登录授权时,将返回的 token 存储在 TokenService 中进行管理。
this.tokenService.set({
token: res.data.token
});
Interceptor
拦截器将每一个请求的头中放入 Authorization 信息。
setReq(req: HttpRequest<any>, options: DelonAuthConfig): HttpRequest<any> {
return req.clone({
setHeaders: {
Authorization: `Bearer ${this.model.token}`,
},
});
}
Refresh Token
如果返回的 Response 的 Header 中有 Authorization 信息,说明请求中带的 Token 已经过期,服务器将 刷新的 Token 放在了 Response 头中。前端获取后更新 Token 。
let newAuth = event.headers.get('Authorization');
if (newAuth) {
let token = newAuth.slice(7);
this.tokenService.set(Object.assign(this.tokenService.get(), {
token: token,
}));
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: