HTTP头中的Set-Cookie在AngularJS中被忽略。

68
我正在开发一个基于AngularJS的应用程序,客户端使用Java编程语言,服务端使用Tomcat和Jersey实现API(Web Service)。
我的API中有一些路径是受限制的,如果用户没有会话,则返回401状态码。在客户端,拦截401 HTTP状态码并将用户重定向到登录页面。
一旦用户被验证,我在服务器端创建一个会话:
httpRequest.getSession(true);
并且响应发送到客户端的头部包含Set-cookie指令:
Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; Path=/api/; HttpOnly
问题是cookie从未放在客户端上。当我检查本地主机域的cookie时,它是空的,因此下一个请求的头部中没有这个cookie,客户端仍然无法访问我的API的受限路径。
客户端和服务器位于同一域,但它们不具有相同的路径和端口号:
客户端:http://localhost:8000/app/index.html 服务器:http://localhost:8080/api/restricted/ 附加信息:CORS在双方都已启用。
"Access-Control-Allow-Methods", "GET, POST, OPTIONS" "Access-Control-Allow-Origin", "*" "Access-Control-Allow-Credentials", true

如何使Set-cookie正常工作? 这是AngularJS相关问题吗?


我不确定它是否仍然相关,但是让你的API依赖于状态是一个糟糕的设计选择(商标)。为了避免未来的问题,你应该使你的API无状态。 - sberder
6个回答

21

我发现了一个AngularJS中的问题,这帮助我向前迈进。

似乎客户端没有设置"Access-Control-Allow-Credentials" : true。指令$httpProvider.defaults.withCredentials = true被忽略了。

我用{withCredentials:true}在配置参数中将$resource调用替换为简单的$http调用。


3
如果您已经找到解决方案,请不要忘记发布答案。如果您的客户端是移动设备,并且您正在使用PhoneGap/Cordova或本机客户端,您是否尝试覆盖来源/引用标头并使用固定来源?请注意保持内容原意不变,使译文更加通俗易懂。 - Ovidiu Buligan
1
您可以在后端获取来源,然后发送相关的标头。以下是一个express.js示例:var origin = req.headers.origin; res.header('Access-Control-Allow-Origin', origin); - Lauris
您似乎已经在回答中发布了一个回答和一个问题。如果要提出新问题,请使用页面顶部的“提问”按钮。如果需要提供上下文,可以链接到此问题。 - elixenide
1
当 "Access-Control-Allow-Credentials" 为 true 时,您必须明确返回正确的域名,即 ""Access-Control-Allow-Origin"。星号是不允许的。因此,人们为了拥有多个允许的来源,会编写一个函数来拦截请求来源,验证该来源是否在您的允许列表中,并将请求来源设置为 "Access-Control-Allow-Origin"。例如,我想让 localhost 和 example.com 正常工作。因此,当来自 localhost 的请求进入时,我的头部必须是 "Access-Control-Allow-Origin", "localhost",对于 example.com,则是 "Access-Control-Allow-Origin", "example.com"。 - Jan Weitz

18

我曾经解决了一个和你类似的问题。

我的Play!后端尝试设置一个会话Cookie,但我无法在Angular中捕获它或通过浏览器存储它。

实际上,这个解决方案涉及到了一些这个、那个。

假设您已经解决了最初的问题,方法是将特定域添加到Access-Control-Allow-Origin并删除通配符,接下来的步骤如下:

  1. 您必须从Set-Cookie标头中移除HTTP-Only,否则您将永远无法收到由您的Angular代码生成的cookie。
    此设置将在Firefox中工作,但在Chrome中不起作用。

  2. 为了使Chrome也能够正常工作,您需要:

    a)在cookie中使用与"hosted" WS相同的域,而不是使用localhost。您甚至可以使用通配符,例如.domain.com而不是ws.domain.com

    b)然后,您需要调用指定在cookie中指定的域,否则Chrome不会存储您的cookie

    [可选]我建议使用/路径代替/api路径。

然后就可以解决问题了。
希望这有所帮助。


我认为问题在于路径是/api,应该是/。 - Seth M.
1
当你说“在cookie中发送与localhost不同的域,使用WS托管的域”时,你是指前端主机还是后端主机? - Pathsofdesign
通常情况下,应该是你的后端,但这实际上取决于你的服务器架构。 你不知道你的 Web 服务在哪里吗? - domokun

14

在客户端的POST请求中,请确保添加以下内容:

对于jQuery AJAX请求:

$.ajax({
  url: "http://yoururlgoeshere",
  type: "post",
  data: "somedata",
  xhrFields: {
    withCredentials: true
  }
});

使用Angular的 $http 服务:

$http.post("http://yoururlgoeshere", "somedata", {
  withCredentials: true
});


我发现在我的情况下,xhrFields:{...}会导致一些问题(我的服务器响应类似于未知来源*),所以我使用了这个代替beforeSend: function(xhr){ xhr.withCredentials = true; },它运行良好。 - medBouzid
我花了一天以上的时间才意识到withCredentials = true不仅告诉浏览器在请求中附加cookie,还告诉它在响应中设置新的cookie。对于 fetch 函数的 ajax 选项也是如此,在请求和响应中发送和设置cookie必须应用credentials: 'include' - 0xC0DEGURU

10

你需要在服务器端和客户端都进行工作。

客户端

通过以下方式之一设置$http配置的withCredentialstrue

  1. 对于每个请求

var config = {withCredentials: true};
$http.post(url, config);
  • 所有请求均适用

  • angular.module("your_module_name").config(['$httpProvider',
      function($httpProvider) {
        $httpProvider.interceptors.push(['$q',
          function($q) {
            return {
              request: function(config) {
                config.withCredentials = true;
                return config;
              }
            };
          }
        ]);
      }
    ]);
    

    服务器

    将响应头Access-Control-Allow-Credentials设置为true


    2
    不需要创建拦截器,现在您可以使用 $httpProvider.defaults.withCredentials = true; - vs4vijay
    我花了一天多的时间才意识到 withCredentials = true 不仅告诉浏览器在请求中附加 cookies,还告诉它在响应中设置新的 cookies。对于 fetch 函数的 ajax 选项也是如此,必须应用 credentials: 'include' 才能使浏览器在请求和响应中发送和设置 cookies。例如,当向专用 API 发送“登录”ajax 请求时,虽然不需要 cookies,但必须设置 withCredentials=true,否则将接收到 sessionid cookie,但浏览器不会保存它。 - 0xC0DEGURU

    8

    添加HttpOnly表示浏览器不应让插件和JavaScript看到cookie。这是用于更安全的浏览的最近惯例。应该用于J_SESSIONID,但可能不适用于此处。


    即使HttpOnly属性不存在,cookie仍不会在客户端设置。 - Romain Lefrancois
    你确定吗?这是一个仅限于 /api/会话(session) cookie。一个实验是使用 response.encodeURL 来处理你的链接。第一个答案可能仍然使用 "...?JSESSIONID=..." 而不是 cookie。但是随后的调用应该已经设置了 cookie。你在浏览器中查看过 cookie 吗? - Joop Eggen
    除了从set-cookie中删除HttpOnly之外,我们还应该删除/更改set-cookie的路径部分为Path=/; 然后它看起来像这样: Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; 所有这些都是在node.js服务器中使用http-proxy完成的,并将仅/api调用传递到后端服务器。 - David
    1
    如果您希望浏览器能够在后续的 XHR 调用中存储和使用 cookie,则还需从 Set-Cookie 中删除 Secure(如果存在)。 - David
    Secure 的意思是当协议不是 https:// 时(包括 ajax 请求),不要将 cookie 附加到 http 请求中。HttpOnly 的意思是 Javascript 无法通过 document.cookie 访问 cookie,但无论如何它都会随着 ajax 请求发送。 - 0xC0DEGURU
    显示剩余3条评论

    2

    我刚刚解决了一个类似的问题。

    我一直在做这件事,但它不起作用...:

      $cookies.put('JSESSIONID', response.data);
    

    Cookie被保存在浏览器中,但是当我发送新请求时,所有的cookie都被发送了,除了我的(我的cookie是JSESSIONID)。

    然后我查看了Chrome检查器,发现了这个:

    我的会话是JsessionID

    问题在于路径不正确!

    然后我尝试了这个方法,我的Cookie被发送了。耶!:

    $cookies.put('JSESSIONID', response.data, {'path':'/'});
    

    我不知道这是否适用于您,但对我有效。
    敬礼!

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接