基于令牌的身份验证中的会话

Sessions in token based authentication

我正在 PHP Lumen 中构建一个应用程序,其中 returns 登录时的令牌。我不确定如何继续进行。

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

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

你可以将它存储在浏览器的localStorage中,然后在每次向服务器请求的header中设置它。

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

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 应用程序中仍然有一个获取实用程序,类似于之前的代码,但我会在选项中发送令牌,方法是在每个请求的 redux 中间件中获取它。

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

首先,当您从 Lumen 收到令牌时,将其保存在特定用户的用户数据库中。然后使用此代码将用户 ID 和访问令牌设置为在一定时间后过期的 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 是否存在,如果存在,它会再次检查该令牌是否与用户数据库中的当前令牌匹配。如果匹配,则显示 'logged in' 页面。如果没有,您应该show/redirect 登录页面。

<?php
if (isset($_COOKIE['accesstoken']))
{
//connect to your user database and check that the cookie accesstoken matches
// if it doesn't match, deal with it appropriately, such as deleting all cookies then redirecting to login page.
}
?>
<!DOCTYPE HTML>
<html>
<head>
<title>Sup</title>
</head>
<body>
<?php if (isset($_COOKIE['accesstoken'])){ ?>

<h1>User logged in!</h1>
<h3>Do whatever you need to do if user is logged in</h3>

<?php } else { ?>

<h1>No accesstoken found</h1>
<h3>More than likely you will want to show login page here</h3>

<?php } ?>
</body>
</html>

然后注销很简单。下面的代码通过将访问令牌设置为过期来删除访问令牌:

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

请记住,这是功能登录/注销系统的基础。如果我解释所有需要的安全措施,这个 post 会更长。一定要做你的研究。开始的一些主题是准备好的语句和防止 XSS 攻击。 :)

我会写下一个快速待办事项和最佳实践,因为有很多方法可以用代码来完成。

后端

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

客户

  • 登录请求,传递{电子邮件,密码}。

    成功后,获取token并存储到本地,首选localstorage,也可以使用cookie

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

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

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

  • 确保对于“已验证”页面,检查令牌是否存在,甚至可以进一步检查用户类型。

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

我最近完成了一个 React 门户网站,我们在其中使用 JWT 来启动、维护和终止用户的 session。

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

代码片段

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

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

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

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

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

当然你可以定义一个通用函数,你可以在每次 API 调用后 set/refresh 令牌。与检索类似,因为在进行 API 调用之前需要令牌。

目前正在为 API 使用 lumen 开发相同类型的应用程序。按照 Lumen with JWT 中基于令牌的身份验证的 3 个步骤:

1.登录成功后创建Token和return

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。定义令牌验证中间件

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 或 cookies

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。在 headers

中随每个请求发送令牌

自定义请求 headers

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

Headers 每个请求

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

希望对您有所帮助。

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

For encryption and decryption you can use in built laravel's Crypt Model

使用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 或您想要的任何内容。

How to parse data on server side

使用命令创建中间件: php artisan make:middleware 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);
}

希望这会有所帮助:)

假设您想构建一个应用程序。

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

1。简介

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

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

2。身份验证

客户只想用username & password换取token

这是一个 HTTP 请求示例

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

响应是:

{
    "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

3。让我们进入 request/response

的更多细节

我们的API将如何处理身份验证请求?

  1. 它将检查用户名 foo 和密码 bar 的用户是否已创建并且在 DB

  2. 中处于活动状态
  3. 它将生成一个 JWT (Json Web Token)

  4. 它将return包含JWT的响应

这是一些超级简单的验证方法,仅作为示例。

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 包来生成验证 JWT 而不是自己编写。

看看这个package,你就知道是怎么做到的了。

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

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

5。在客户端保存 JWT

它们是您已经知道的两种方式 localStorage & cookies 对我来说,我正在使用 cookie,因为:

  1. 他们多了一点
  2. 可以在不实现某些自定义逻辑的情况下设置到期日期。
  3. 旧版浏览器支持(很旧的浏览器,所以没那么重要)。

但这完全取决于你。

6。使用 JWT

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

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

示例请求:

  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 过期日期。