tomcat应用中用户session被重置的问题

一个tomcat应用服务器中只有一个webapp项目名为app,tomcat前面有一个反向代理服务器nginx,nginx将所有访问http://www.example.com/下的请求转发给后台的tomcat应用服务器:http://www.example.com/app/,具体配置如下:

location / {    rewrite ^/(.*)$ /app/$1;    break;}

当前用户已经在http://www.example.com/app/中登录,客户端cookie写入成功,因为是app写入的cookie,所以其作用路径即path=/app/

当用户访问http://www.example.com/app/some/page页面时,当前的用户session就被重置了,而访问其他页面都是正确的。

检查所在页面的代码,发现页面上有个图片链接因为远程获取用户头像的数据不对,写入的地址变成src="/0",正确应该是用户的头像地址,就是因为访问了这个错误的地址http://www.example.com/0导致用户的session被重置了。

问题重现

用户在请求链接http://www.example.com/app/some/page时的请求头是正常的携带了Cookie信息:

Request Headers

           Host: www.example.com     User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36         Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2,ja;q=0.2,sv;q=0.2,hu;q=0.2         Cookie: JSESSIONID=f6fdc154-2bd5-45ad-b363-98226db47baf

但是页面里那个图片引发的请求地址http://www.example.com/0是不存在的,其请求是从当前域名根目录开始访问的,域名根目录下是没有cookie的,因为JSESSIONID是写在/app/这个path下的。其请求头如下,相比上面那个请求少了一个Cookie头信息:

           Host: www.example.com         Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2,ja;q=0.2,sv;q=0.2,hu;q=0.2     User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36

因为nginx上url重写的规则如下:

rewrite ^/(.*)$ /app/$1;

根据这个重写规则,nginx直接在服务端转发请求/0给tomcat中的应用,即/app/0,这个操作不会经过浏览器301或者302跳转,tomcat发现请求头里没有Cookie,就会在Response头里加上Set-Cookie字段,这样就导致当前用户的session被重置了,响应头内容如下:

Response Headers

      Connection: keep-aliveContent-Language: en    Content-Type: text/html;charset=ISO-8859-1            Date: Wed, 20 Sep 2017 05:36:11 GMT          Server: nginx/1.8.0      Set-Cookie: JSESSIONID=92afd578-f3b1-425f-a9d1-72ee4b1bfd7b; Path=/app/; HttpOnly

问题修复

这个问题主要是因为服务器内部转发的请求/app/0并没有将客户端的Cookie信息带过去造成的,因此不能直接在服务器端将url重写后转发给tomcat服务器。

将nginx的配置部分稍作改动,将请求变成301重定向:

rewrite ^/(.*)$ /app/$1 permanent;break;

或者是302重定向:

rewrite ^/(.*)$ /app/$1 redirect;break;

使用这个配置,nginx会重定向到客户端浏览器进行一次301或者302跳转操作,这时浏览器再去访问http://www.example.com/app/0时,会携带本地的Cookie发送到服务器端。

           Host: www.example.com     User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36         Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2,ja;q=0.2,sv;q=0.2,hu;q=0.2         Cookie: JSESSIONID=92afd578-f3b1-425f-a9d1-72ee4b1bfd7b

服务器端tomcat会收到浏览器重定向后的请求,并正确收到Cookie,就不会再重置用户session。

rewrite ^/(.)$ /app/$1;
rewrite ^/(.
)$ /app/$1 redirect;

以上二者的区别就在于加了redirect指令会让浏览器多发起了一次请求,进行302重定向,不加指令则直接在服务器内部进行了URL重写转发,不会让浏览器重定向。

rewrite 规则

rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向,这里要区别重写与重定向的作用是不同的。语法形式如下:

Syntax: rewrite regex replacement [flag];
Default: —
Context: server, location, if

表面看rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。很多情况下rewrite也会写在location里,它们的执行顺序是:

  1. 执行server块的rewrite指令
  2. 执行location匹配
  3. 执行选定的location中的rewrite指令

如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;循环超过10次,则返回500 Internal Server Error错误。

rewrite flag 标志位

  1. last: 停止处理这次的rewrite指令集,根据修改之后的URI重新查找匹配的location。
    • stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI;
  2. break: 停止执行当前虚拟主机的后续rewrite指令集。
    • stops processing the current set of ngx_http_rewrite_module directives as with the break directive;
  3. redirect: 返回302临时重定向,地址栏会显示跳转后的地址。
    • returns a temporary redirect with the 302 code; used if a replacement string does not start with "http://", "https://", or "$scheme";
  4. permanent: 返回301永久重定向,地址栏会显示转后的地址。
    • returns a permanent redirect with the 301 code.

last 和 break 区别

  1. last一般写在server和if中,而break一般使用在location中。
  2. last不终止重写后的url匹配,即新的url会再从server和location开始进行下一轮的匹配流程,而break终止重写后的匹配。
  3. break和last都能中止当前rewrite指令集的匹配。
  4. 编程语言中的continue和break的区别。

References

  1. Module ngx_http_rewrite_module
  2. nginx配置location总结及rewrite规则写法