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

时间:2017-08-01 19:15:22

标签: php jquery reactjs authorization lumen

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

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

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

7 个答案:

答案 0 :(得分:11)

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

await Task.WhenAll(..)

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

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

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

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

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

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

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

答案 1 :(得分:5)

我们假设您要使用。

构建APP
  1. ReactJS
  2. 使用PHP的REST API
  3. 使用JWT
  4. 1。引言

    构建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已成立且在DB

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

    4. 它将返回包含JWT

    5. 的响应

      这是一些超级简单的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软件包来生成验证 JWT,而不是自己编写。

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

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

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

      5。在客户端保存JWT

      你知道localStorage& cookies 对我来说,我正在使用cookies,因为:

      1. 他们多一点secure
      2. 可以在不实施某些自定义逻辑的情况下设置过期日期。
      3. 较旧的浏览器支持(非常旧的浏览器,因此它并不重要)。
      4. 但这完全取决于你。

        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来保存JWT,请注意设置过期日期。

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

答案 2 :(得分:3)

目前正在使用流明的API处理相同类型的应用程序。在Lumen with JWT中执行基于令牌的身份验证的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。定义用于令牌验证的中间件

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。发送包含标题中每个请求的令牌

使用自定义标题的请求

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

每个请求的标头

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

希望它会有所帮助。

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

答案 3 :(得分:2)

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

答案 4 :(得分:2)

  

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

中使用

使用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);
}

希望这会有所帮助:)

答案 5 :(得分:0)

我会写下快速的todo和最佳做法,因为有很多方法可以用代码来完成。

后端

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

客户端

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

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

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

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

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

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

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

答案 6 :(得分:0)

我最近完成了一个反应门户网站,我们使用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 store)。
  8. 如果发生任何刷新,我们会从会话存储中检索令牌并调度初始操作以再次构建状态(redux存储)。如果任何操作(API)失败,我们会向用户显示会话已过期或无效的消息,您需要登录才能将用户重新发送回登录/主页。
  9. 代码段

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

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

    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调用之前需要令牌。