thinkphp jwt鉴权实现 # 前言 公司以前一直使用userId与sessionId保持向后端分离时的用户状态。 > 前端在登录时会生成一串sessionId,保存到数据库,之后用户请求接口时携带userId与sessionId,系统判断是否匹配,并且是否在有效期内,如匹配则放行。 如果用户第二次登录,会重置掉第一次sessionId,以保持线上只有一个用户在登录。 这种方式和jwt的方式很像,但是在安全性,携带信息等方面还是不如jwt。 刚好公司后台框架升级,趁着这次机会引入jwt。 # jwt是什么? > JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。 参考文章: 传送门 # 背景 后台框架:thinkphp5.1 前端框架:react+ant.design 前后端分离,并使用跨域方案实现前后端的分离 # 实现 ## 引入第三方jwt库 `composer require firebase/php-jwt` ## 在extend下新建jwt/jwtToken.php ```php "chenls", #非必须。issued at。 token创建时间,unix时间戳格式 "iat" => $now, #非必须。expire 指定token的生命周期。unix时间戳格式 "exp" => $now + $this->expiredTime, # 非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。 "nbf" => $now - 60, // #非必须。接收该JWT的一方。 // "aud" => "http://example.com", // #非必须。该JWT所面向的用户 // "sub" => "jrocket@example.com", # 非必须。JWT ID。针对当前token的唯一标识 // "jti" => '222we', 'data' => $data ]; $token = JWT::encode($jwt, $this->privateKey, $type); Cache::set($token, $data, $this->expiredTime + 600); return $token; } /** * 解密 */ public function decode($token) { JWT::$leeway = 60; $tokenData = JWT::decode($token, $this->publicKey, array('RS256')); return $tokenData; } } ``` ## 新建中间件 ### 处理方式 前端在每次请求时往header塞入authorization:"jwtToken",后端拿到token进行解密,如正常解密,则把userId塞入请求中(userId不会暴露在接口中,也不需要前端发送),并通过此次请求。 如果token能被正常解密,但是token是异常的(一般是过期了),则判断缓存中是否有这个token(因为缓存中token比实际过期时间多10分钟,超过10分钟不在理会),有的话放行此次请求,但是会在响应头header上塞入新的authorization:"jwtToken",前端看到响应头有新的token,则需要替换老的,以实现用户无缝操作不掉线。 如果不能解密,则拦截。 ### 代码 中间件内处理token是否能正常解密,此处代码放在中间件中。 ```php $token = $request->header('authorization'); if (empty($token)) { return json(ajaxReturn(-10000, "无参数token")); } //方便开发人员自己调试接口 if ($token == '000000' && Env::get('APP_STATUS') !== 'online') { $request->memberId = 1; $request->memberName = "开发人员"; return $next($request); } $token = trim(ltrim($token, 'Bearer')); try { $jwt = new \jwt\JwtToken(); $token = $jwt->decode($token); $token = (array)$token; //判断用户是否存在 if (empty($token['data']->memberId)) { return json(ajaxReturn(-10000, "非法用户")); } $count = model('member')->where('id', $token['data']->memberId)->count(); if ($count == 0) { return json(ajaxReturn(-10000, "不存在该用户")); } $request->memberId = $token['data']->memberId; $request->memberName = $token['data']->memberName; $request->roleCodes = $token['data']->roleCodes; } catch (ExpiredException $th) { if (Cache::has($token)) { $tokenData = Cache::get($token); $request->memberId = $tokenData['memberId']; $request->memberName = $tokenData['memberName']; $request->roleCodes = $tokenData['roleCodes']; $response = $next($request); //自动延长token $newToken = $jwt->encode($tokenData); $header = [ 'Access-Control-Expose-Headers' => 'authorization', 'Cache-Control' => 'no-store', 'authorization' => 'Bearer ' . $newToken ]; return $response->header($header); } else { return json(ajaxReturn(-10000, "账号信息过期了,请重新登录")); } } catch (\Exception $th) { return json(ajaxReturn(-10000, "账号信息过期了,请重新登录")); } return $next($request); ``` # 后记 此次使用jwt耗时最久的就是当token过期后,而用户还在操作,我们不希望用户重登系统,需要做到自动延期并重新分发token。 目前上述代码未实现同一个账号只有一个人在线功能。如需要此功能,可在登录时强制保持一个用户只有一个token即可。