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

IT技术 php jquery reactjs authorization lumen
2021-05-17 03:49:33

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

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

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

4个回答

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

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

让我们假设你想用它来构建一个 APP。

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

一、介绍

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

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

2. 认证

客户想要做的只是交换一些username&password以获得令牌。

这是一个示例 HTTP 请求

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

回应是:

{
    "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

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

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

  1. 它将检查是否建立了具有用户名foo和密码的用户bar并且它在数据库中处于活动状态

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

  3. 它将返回包含 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,而不是自己编写。

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

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

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

5. 在客户端保存 JWT

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

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

目前正在使用 lumen for API 开发相同类型的应用程序。使用 JWTLumen 中进行基于令牌的身份验证的以下 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. 将令牌存储在 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}
    });

希望它会有所帮助。

注意:在从localstorage读取数据时添加一些检查和数据验证cookies

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