具有基于 HttpOnly cookie 的身份验证和会话管理的单页应用程序

Posted

技术标签:

【中文标题】具有基于 HttpOnly cookie 的身份验证和会话管理的单页应用程序【英文标题】:Single page application with HttpOnly cookie-based authentication and session management 【发布时间】:2017-08-07 01:22:04 【问题描述】:

几天来,我一直在为我的单页应用程序寻找一种安全的身份验证和会话管理机制。从大量关于 SPA 和身份验证的教程和博客文章来看,将 JWT 存储在 localStorage 或常规 cookie 中似乎是最常见的方法,但 it's simply not a good idea to use JWTs for sessions 所以它不是这个应用程序的选项。

要求

登录应该是可撤销的。例如,如果在用户帐户上检测到可疑活动,应该可以立即撤消该用户的访问权限并要求重新登录。同样,密码重置等操作应该撤消所有现有登录。 应该通过 Ajax 进行身份验证。之后不应发生浏览器重定向(“硬”重定向)。所有导航都应在 SPA 内进行。 一切都应该跨域工作。 SPA 将在 www.domain1.com 上,服务器将在 www.domain2.com 上。 javascript 不应访问服务器发送的敏感数据,例如会话 ID。这是为了防止恶意脚本可以从常规 cookie、localStorage 或 sessionStorage 中窃取令牌或会话 ID 的 XSS 攻击。

理想的机制似乎是使用包含会话 ID 的 HttpOnly cookie 的基于 cookie 的身份验证。流程将像这样工作:

    用户到达登录页面并提交其用户名和密码。 服务器对用户进行身份验证并将会话 ID 作为 HttpOnly 响应 cookie 发送。 然后 SPA 将此 cookie 包含在对服务器发出的后续 XHR 请求中。使用withCredentials 选项似乎可以做到这一点。 当向受保护的端点发出请求时,服务器会查找 cookie。如果找到,它会根据数据库表交叉检查该会话 ID,以确保该会话仍然有效。 当用户注销时,会话将从数据库中删除。用户下次访问网站时,SPA 会收到 401/403 响应(因为会话已过期),然后将用户带到登录屏幕。

因为 cookie 带有 HttpOnly 标志,JavaScript 将无法读取其内容,因此只要通过 HTTPS 传输它就不会受到攻击。

挑战

这是我遇到的具体问题。我的服务器配置为处理 CORS 请求。在对用户进行身份验证后,它会在响应中正确发送 cookie:

HTTP/1.1 200 OK
server: Cowboy
date: Wed, 15 Mar 2017 22:35:46 GMT
content-length: 59
set-cookie: _myapp_key=SFMyNTYBbQAAABBn; path=/; HttpOnly
content-type: application/json; charset=utf-8
cache-control: max-age=0, private, must-revalidate
x-request-id: qi2q2rtt7mpi9u9c703tp7idmfg4qs6o
access-control-allow-origin: http://localhost:8080
access-control-expose-headers: 
access-control-allow-credentials: true
vary: Origin

但是,浏览器不保存 cookie(当我检查 Chrome 的本地 cookie 时,它​​不存在)。所以当下面的代码运行时:

context.axios.post(LOGIN_URL, creds).then(response => 
  context.$router.push("/api/account")

帐户页面已创建:

created() 
  this.axios.get(SERVER_URL + "/api/account/", withCredentials: true).then(response => 
    //do stuff
  

此调用的标头中没有 cookie。服务器因此拒绝它。

GET /api/account/ HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Accept: application/json
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,tr;q=0.6

我是否需要做一些特别的事情来确保浏览器在登录响应中收到 cookie 后保存它?我读过的各种资料都说浏览器仅在用户被重定向时才保存响应 cookie,但我不希望任何“硬”重定向,因为这是一个 SPA。

有问题的 SPA 是用 Vue.js 编写的,但我想它适用于所有 SPA。我想知道人们如何处理这种情况。

我读过的关于这个主题的其他内容:

SPA best practices for authentication and session management Are HTTPOnly Cookies submitted via XmlHTTPRequest with withCredentials=True? Stop using JWT for sessions, part 2: Why your solution doesn't work

【问题讨论】:

嗨!您找到此问题的原因了吗? 您找到解决方案了吗? 【参考方案1】:

我在 Vue.js 2 上使用了 credentials = true。从客户端设置凭据只是故事的一半。您还需要从服务器设置响应标头:

    header("Access-Control-Allow-Origin: http://localhost:8080");
    header("Access-Control-Allow-Credentials: true");

您不能像这样对 Access-Control-Allow-Origin 使用通配符:

header("Access-Control-Allow-Origin: *");

当你指定credentials: true header时,你需要指定orgin。

如您所见,这是一个 php 代码,您可以将其建模为 NodeJS 或您正在使用的任何服务器端脚本语言。

在 VueJS 中,我在 main.js 中像这样设置了 credentials = true:

Vue.http.options.credentials = true

在组件中,我使用ajax成功登录:

<template>
<div id="userprofile">
    <h2>User profile</h2>
    init()

</div>
</template>

<script>
    export default   
        name: 'UserProfile',
        methods: 
        init: function() 


                // loggin in
              console.log('Attempting to login');

            this.$http.get('https://localhost/api/login.php')
            .then(resource =>  
             // logging response - Notice I don't send any user or password for testing purposes  

            );

            // check the user profile

                console.log('Getting user profile');

                this.$http.get('https://localhost/api/userprofile.php')
                .then(resource =>  
                    console.log(resource.data);
                )

        
    
    

</script>

在服务器端,事情很简单: Login.php on 设置一个cookie而不进行任何验证(注意:这仅用于测试目的。不建议您在未经验证的情况下在生产中使用此代码)

<?php

header("Access-Control-Allow-Origin: http://localhost:8080");
header("Access-Control-Allow-Credentials: true");

$cookie = setcookie('user', 'student', time()+3600, '/', 'localhost', false , true);

if($cookie)
  echo "Logged in";
else
  echo "Can't set a cookie";

最后,userprofile.php 只是验证是否设置了 cookie = user

<?php

  header("Access-Control-Allow-Origin: http://localhost:8080");
    header("Access-Control-Allow-Credentials: true");


if(isset($_COOKIE['user']))
  echo "Congratulations the user is ". $_COOKIE['user'];

else
  echo "Not allowed to access";

登录成功

【讨论】:

如果用户已登录,如何在 vue 中检查我的客户端?所有其他使用 JWT 的解决方案只是检查它们是否可以在本地存储中找到令牌,如果可以,则得出用户必须登录的结论。【参考方案2】:

使用凭据控制设置和发送 cookie,因此请务必在发布到登录时将其打开,以便将返回的 cookie 保存在浏览器中。

【讨论】:

以上是关于具有基于 HttpOnly cookie 的身份验证和会话管理的单页应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何将 window.fetch() 与 httpOnly cookie 或基本身份验证一起使用

SessionAuthenticationModule Cookie 处理程序未创建 HttpOnly 安全 cookie

如何在 React/NodeJS 身份验证流程中使用 httpOnly cookie 获取用户数据

HttpOnly 与cookie安全

Laravel 5.6 - Passport JWT httponly cookie SPA 身份验证用于自用 API?

在 Django/DRF 中使用 JWT 身份验证并将 JWT 存储在 HttpOnly Cookie 中