基于令牌的身份validation中的会话

我正在PHP Lumen中构建一个应用程序,它在登录时返回一个令牌。 我不知道如何超越这个。

我该如何使用这些令牌维护会话?

具体来说,如果我使用reactjs或vanilla HTML / CSS / jQuery,如何在我为Web应用程序的安全部分发出的每个请求中发送它们,如何在客户端存储令牌?

我通常做的是将令牌保留在本地存储中,这样即使用户离开站点,我也可以持久化令牌。

 localStorage.setItem('app-token', theTokenFromServer); 

每次用户加载页面时,我要做的第一件事就是查找令牌的存在。

 token = localStorage.getItem('app-token'); 

如果使用react,我会将令牌保持在全局状态(例如使用redux):

 function loadAppToken(token) { return { type: 'LOAD_TOKEN', payload: { token }, }; } 

使用vanilla javascript我会将它保存在我的连接实用程序中。 这可能类似于以下内容:

 const token = localStorage.getItem('app-token'); export function request(config) { const { url, ...others } = config; return fetch(url, { ...others, credentials: 'include', headers: { 'Authorization': `Bearer ${token}` }, }); } 

我在react应用程序中仍然有一个fetch实用程序,类似于前面的代码,但我会在选项中发送令牌,通过在每个请求的redux中间件中获取它。

目前正在使用流明的API处理相同类型的应用程序。 使用JWT在流明中使用基于令牌的身份validation的3个步骤:

1.创建令牌并在登录成功后返回

 public function login(Request $request) { $token = $this->jwt->attempt(['user_name' => $data['user_name'], 'password' => $data['password']]); //$token = $this->jwt->attempt($data); if (!$token) { $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_INVALID_USER, 'error' => array(Messages::MSG_INVALID_USER))); return response()->json($response); } else { $user = \Auth::setToken($token)->user(); $data = array('token' => $token,'user_id' => $user->id); $response = array('success' => true, 'data' => $data, 'detail' => array('message' => Messages::MSG_SUCCESS, 'error' => null)); return response()->json($response); } } 

2.定义用于令牌validation的中间件

 public function handle($request, Closure $next, $guard = null) { try { $token = $request->header('X-TOKEN'); $user_id = $request->header('X-USER'); $user = \Auth::setToken($token)->user(); if ($user && $user->id == $user_id) { return $next($request); } else { $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERR_INVALID_TOKEN, 'error' => Messages::MSG_ERR_INVALID_TOKEN)); return response()->json($response); } } catch (Exception $ex) { $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERROR_500, 'error' => array($ex))); return response()->json($response); } } 

3.将令牌存储在localstorage或cookie中

 localStorage.setItem("Token", JSON.stringify(TokenData)); TokenData = JSON.parse(localStorage.getItem("Token")); 

要么

 $.cookie('Token', JSON.stringify(TokenData), {expires: 1, path: '/'}); TokenData = JSON.parse($.cookie("Token")); 

4.在标头中发送每个请求的令牌

请求自定义标头

 $.ajax({ url: 'foo/bar', headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId} }); 

每个请求的标题

 $.ajaxSetup({ headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId} }); 

希望它会有所帮助。

注意:在从localstoragecookies读取数据时添加一些检查和数据validation。

我们假设您要构建一个APP。

  1. ReactJS
  2. 使用PHP的REST API
  3. 使用JWT

1.简介

构建REST API时必须忘记会话。

REST API是无状态的,因此它们不能依赖于会话,它们必须仅使用客户端提供的数据来处理请求。

2.认证

所有客户想要做的只是交换一些令牌的usernamepassword

这是一个示例HTTP请求

 POST /api/v1/authentication HTTP/1.1 Host: localhost Content-Type: application/json { "username": "foo", "password": "bar" } 

响应是:

 { "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } 

3.让我们详细了解请求/响应

我们的API如何处理身份validation请求?

  1. 它将检查用户名为foo &password bar的用户是否已建立且在DB中是否处于活动状态

  2. 它将生成一个JWT(Json Web Token)

  3. 它将返回包含JWT的响应

这是一些超级简单的auth方法,仅举例来说。

 public function authAction() { /** Get your payload somehow */ $request = $_POST; //Validate if username & password are given/ $user = $this->model->auth($username, $password); if(!$user) { //throw error for not valid credentials } $jwt = $this->jwt->create($user); //return response with $jwt } 

如你所见,它们不是会话集或任何东西。

我们的客户方将如何处理响应?

客户端可以使用像superagent这样的包来处理请求和对API的响应,这样过程将简化为:

  let data = { username: email, password: password }; request .post('/api/v1/authentication') .set('Content-Type', 'application/json') .send(data) .end(function (error, response) { //response.body.token }); 

4.在服务器端创建JWT

您可以使用一些3RD PT包来生成validation JWT,而不是自己编写。

看看这个包 ,你可以看到它是如何完成的。

并记住要始终创建强大的签名。 我建议使用RSA keys

我不是在宣传或支持这个项目,只是觉得在这里分享它很有用。 我从未使用它,我在NodeJS项目中使用类似的东西。

5.在客户端保存JWT

他们有两种方式,因为你已经知道localStoragecookies对我来说,我使用的是cookies,因为:

  1. 它们更安全一点 。
  2. 可以在不实现某些自定义逻辑的情况下设置过期日期。
  3. 较旧的浏览器支持(非常古老的浏览器,所以它并不重要)。

但这完全取决于你。

6.使用JWT

从现在开始,每个服务器请求都必须包含您的JWT。

在REST API中,您必须编写一个方法来validationJWT并将其交换为用户对象。

示例请求:

  let jwt = ...; //GET IT FROM LOCALSTORAGE OR COOKIE request .get('/api/v1/posts') .set('Content-Type', 'application/json') .set('Authorization', jwt) .end(function (error, response) { }); 

API将如何处理此请求

 public function postsAction() { $jwt = $this->headers->get('Authorization'); if(!$this->jwt->validate($jwt)) { //throw unauthorized error } $user = $this->model->exchangeJWT($jwt); //Your logic here } 

7.过期日期和cookie

如果您使用cookie来保存JWT,请注意设置过期日期。

Cookie过期日期必须等于JWT过期日期。

您可以将其存储在浏览器的localStorage中,然后在每个请求到服务器的标头中设置它。

你实际上不需要任何ReactJS或VanillaJS。 实际上只是纯HTML和PHP。 我所做的只是将其存储为cookie。

首先,当您从Lumen收到令牌时,将其保存在特定用户的用户数据库中。 然后将用户ID和accesstoken设置为cookie,使用此代码在一段时间后过期:

 setcookie('userid',$userid, time()+(3600 * 24 * 15),"/"); setcookie('accesstoken',$accesstoken, time()+(3600 * 24 * 15),"/"); header('Location: /home.php'); //You can change the 15 in setcookie() to amount of days the cookie will expire in. //The "/" in setcookie is important, because it ensures the cookies will be available on every page the user visits on your website. //The header function redirects to your home page after log in 

接下来是您的主页的外观。 它检查是否存在accesstoken cookie,如果存在,它会双重检查该令牌是否与用户数据库中的当前令牌匹配。 如果匹配,则会显示“已登录”页面。 如果没有,您应该显示/重定向到登录页面。

     Sup    

User logged in!

Do whatever you need to do if user is logged in

No accesstoken found

More than likely you will want to show login page here

然后注销很简单。 下面的代码通过将它们设置为过期来删除accessstokens:

 setcookie("accesstoken", "", time() - 3600); setcookie("userid", "", time() - 3600); header('Location: /youareloggedout.html'); 

请记住,这是function登录/注销系统的基础。 如果我解释了所需的所有安全措施,这个post会更长。 一定要做你的研究。 启动你的一些主题是预备语句和防止XSS攻击。 🙂

对于加密和解密,您可以在内置的laravel的Crypt模型中使用

使用Illuminate \ Support \ Facades \ Crypt;

我们为生成API令牌所做的工作将需要一系列必需的字段。

让我们创建数据

 $data = [ 'user_id' => $user->id, 'time_stemp' => \Carbon::now() // Carbon is laravel's time model(class) for managing times 'expire_on' => \Carbon::now()->addDays(2); //here i'm setting token expires time for 2 days you can change any ]; $data = serialize($data); 

然后用Crypt加密你的数据

 $accessToken = Crypt::encrypt($data); 

现在发送到前端作为响应并保存在本地存储或cookie中,此处不需要任何时间只检查服务器。

现在,在每个请求传递该令牌并在服务器端创建一个将解析您的数据的中间件,如果您的令牌时间少于到期时间,则向前移动,否则发送错误403或您想要的任何内容。

如何在服务器端解析数据

使用命令创建中间件: php artisan make:中间件ApiAuth然后是句柄部分

 //Accesstoken you passed in $headers or in $request param use whatever you like $searilizerData = Crypt::decrypt($headers['AccessToken']); $data = unserialize($searilizerData); //check if expire_on is less then current server time if($data['expire_on] <= \Curbon::now()){ next(); // let them contuine and access data } else { throw new Exception ("Your token has expired please regenerate your token",403); } 

希望这会有所帮助:)

我会写下一个快速的todo和最佳实践,因为有许多方法可以用代码完成。

后端

  • (POST)登录路由{email,password}它将创建一个令牌。 您可以使用JWT(Json Web Token)令牌将返回给客户端。 在令牌内,您可以存储一些基本细节:用户ID,用户名,令牌到期,用户类型等.https://jwt.io/

客户

  • 登录请求,通过{email,password}。

    成功时,获取令牌并将其存储在本地,首选localstorage,但也可以使用cookie。

  • 在每个页面加载您的react应用程序时,您应该对该令牌进行function检查,它将对其进行解密,并获取详细信息以供进一步使用。

    我的意思是获取用户名,用户ID等。如果您想要添加它,更重要的是“过期”,如果令牌过期,您将用户重定向到登录页面,或者您可以重新请求新令牌,这真的取决于你的应用程序。

  • 注销,非常简单……只需从客户端删除令牌并重定向到登录页面即可。

  • 确保对于“经过身份validation的”页面,检查令牌是否存在,甚至可以检查用户类型。

**对于JWT的客户端解码,您可以使用: https : //www.npmjs.com/package/jwt-client

我最近完成了一个反应门户网站,我们使用JWT来启动,维护和过期用户的会话。

  1. 登录后,将用户凭据发送到登录API。 成功后,从后端API获取令牌。 后端维护令牌生成和到期。
  2. 将令牌存储在反应状态(我们使用redux存储)和会话存储中(如果页面被刷新,我们可以从会话存储中取回它)。
  3. (可选)在会话存储中启动每秒计数器(以检查用户空闲的时间)
  4. 登录后,每个API调用都需要在标头中发送令牌。 API调用是使用fetch进行的。 如果API调用成功,我们将从后端获取令牌,并将其替换为现有令牌(保持新鲜)。
  5. 所有API调用都是通过通用的customFetch函数“获取”的。 想法是进行通用提取以查看后端响应是否为401(访问被拒绝)。 如果是401,则令牌过期或无效(用户尝试在不登录的情况下访问某些内容)。 在这种情况下,我们将用户从门户网站退出,返回登录/主页(显示访问被拒绝的错误)。
  6. (可选)如果用户空闲时间过长(检查第二个计数器> 900,即15分钟),我们会向用户显示会话即将过期的警告,让用户可以选择继续。 如果用户点击继续,我们会再次调用API来检索用户的个人资料,从而确保该令牌仍然有效。 如果API不成功,我们会将用户注销并发送回登录/主页。 在进行任何API调用之前,第二个计数器会重新设置为1(用户处于活动状态并执行某些操作)。
  7. 不用说,在通过上述任何一种情况将用户发送到登录/主页之前,我们清除会话存储并重置状态(redux存储)。
  8. 如果发生任何刷新,我们从会话存储中检索令牌并调度初始操作以再次构建状态(redux存储)。 如果任何操作(API)失败,我们会向用户显示会话已过期或无效的消息,您需要登录才能将用户重新发送回登录/主页。

代码片段

假设您已从登录API调用中检索到令牌:

在会话存储和状态中设置令牌(redux存储)

 window.sessionStorage.setItem('partyToken', token) store.dispatch({type: 'profile/setToken', payload: { token }}) 

从会话存储或状态(redux存储)检索令牌

 const token = window.sessionStorage.getItem('token') const token = store.getState().profile && store.getState().profile.token 

当然,您可以定义一个通用函数,您可以在每次API调用后设置/刷新令牌。 类似于检索,因为在进行API调用之前需要令牌。