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

Posted

技术标签:

【中文标题】基于令牌的身份验证中的会话【英文标题】:Sessions in token based authentication 【发布时间】:2018-01-08 19:15:06 【问题描述】:

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

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

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

【问题讨论】:

如果 Lumen 使用 cookie,那么它们可能会自动为您设置。 https://lumen.laravel.com/docs/5.4/authenticationhttps://lumen.laravel.com/docs/5.4/authorization 了解Laravel Passport 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

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 中间件中获取它。

【讨论】:

+1 为简单起见。此外,在您的后端,您可以设置 JWT 令牌的到期日期。如果令牌过期,API 将返回未授权,您应该将用户重定向到登录页面。 本地存储可以被页面上的其他脚本读取,通常被认为是不安全的【参考方案3】:

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

首先,当您从 Lumen 收到令牌时,将其保存在您的用户数据库中以供特定用户使用。然后将用户 id 和 accesstoken 设置为 cookie,这些 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 是否存在,如果存在,它会再次检查令牌是否与用户数据库中的当前令牌匹配。如果匹配,则显示“登录”页面。如果没有,您应该显示/重定向到登录页面。

<?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');

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

【讨论】:

【参考方案4】:

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

后端

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

客户

登录请求,传递 email, password。

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

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

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

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

确保对于“经过身份验证的”页面,您检查令牌是否存在,甚至可以进一步检查用户类型。

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

【讨论】:

【参考方案5】:

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

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

代码 sn-ps

假设您已从登录 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 调用之前需要令牌。

【讨论】:

【参考方案6】:

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

1.登录成功后创建Token并返回

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.将令牌存储在本地存储或 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 读取数据时添加一些检查和数据验证。

【讨论】:

【参考方案7】:

加密和解密可以在 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: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);

希望这会有所帮助:)

【讨论】:

【参考方案8】:

假设您要构建一个 APP。

    ReactJS 带有 PHP 的 REST API 使用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 将如何处理身份验证请求?

    它将检查用户名foo和密码bar的用户是否已创建并且在DB中处于活动状态

    会生成一个JWT(Json Web Token)

    它将返回包含 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

您已经知道它们是两种方式localStoragecookies 对我来说,我使用 cookie,因为:

    他们有点多secure。 无需实现一些自定义逻辑即可设置过期日期。 旧版浏览器支持(非常旧的浏览器,所以没那么重要)。

但这一切都取决于你。

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

【讨论】:

以上是关于基于令牌的身份验证中的会话的主要内容,如果未能解决你的问题,请参考以下文章

混合身份验证 - 基于 Spring MVC 会话 + 基于 JWT 令牌

如果我使用基于令牌的身份验证,我还需要会话吗

基本身份验证是基于令牌的身份验证吗?

基于令牌的身份验证的安全性

服务器如何在基于令牌的授权中将 JWT 发送给客户端?

Web 应用程序的基于令牌的身份验证:如何缓存令牌?