一个“403”问题的产生及解决
最近在测试一个项目的时候,遇到了一个比较诡异的“403”问题问题。在经过不断的查找资料和咨询大师级的人物之后,问题终于有了解决方案。现在就把我在整个过程中遇到的坑记录下来,可以让大家后续遇到类似问题有所参考。
问题描述
1、测试的项目是一个程序设计考试的web项目,因此会涉及到用户的登录,查看题目,提交答案,查看题目列表,查看排名等一系列问题;
2、项目开始时,只有一台服务器,这里称为服务器A,包括了web + 判题服务两部分。为了提高项目的性能,新增加了两台服务器,这里称为服务器B和服务器C,同样上面也都部署了web + 判题服务两部分;
3、在扩容前,也就是只有服务器A的时候,测试脚本中的请求发送的URL就是服务器A的IP地址+开放的端口号,这里就用“服务器A_IP :port”来表示;
4、在扩容之后,因为有了服务器A,B,C,势必需要进行负载均衡,于是引入了nginx来做负载均衡。所以在nginx中配置了域名server_name。那么理所当然的,我的测试脚本中的请求发送的URL就是nginx中设置的域名了,这里就用“server_name”来表示;
5、那么问题终于来了,扩容前进行测试一切请求包括,用户的登录,查看题目,提交答案,查看题目列表,查看排名等都很正常。但是扩容之后,在提交答案的这个请求返回状态一直是“403 forbidden”。并且扩容前后测试脚本并没有改动,唯一的改动地方就是请求的URL从“服务器A_IP :port”变成了“server_name”;
解决过程:
1、刚看到这个问题,第一个想法是这样:
由于nginx做了负载均衡,那么一个脚本中的3个请求:request1,request2,request3分别给均衡到了服务器A,服务器B,服务器C上面。所以对于用户perftest1来说,他的登录请求被分配到了服务器A上,查看题目的请求被分配到了服务器B上,提交答案的请求被分配到了服务器C上。被分配到服务器C上的请求request3,仍然在苦苦等着属于他自己的patsid,但是他自己的patsid却在服务器A上,他永远也拿不到,所以产生了 403请求,被服务器以不正确的patsid为由拒绝了perftest1的提交答案的请求。
请教了一些同事后,他们给的建议是采用nginx的sticky模块。在多台后台服务器的环境下,我们为了确保一个客户只和一台服务器通信,我们势必使用长连接。使用什么方式来实现这种连接呢,常见的有使用nginx自带的ip_hash来做,我想这绝对不是一个好的办法,如果前端是CDN,或者说一个局域网的客户同时访问服务器,导致出现服务器分配不均衡,以及不能保证每次访问都粘滞在同一台服务器。如果基于cookie会是一种什么情形,想想看,每台电脑都会有不同的cookie,在保持长连接的同时还保证了服务器的压力均衡,nginx sticky值得推荐。如果浏览器不支持cookie,那么sticky不生效,毕竟整个模块是给予cookie实现的。nginx sticky 模块工作流程图如下:
但是,在nginx的配置文件中新增了sticky模块之后,问题并没有解决。方案一以失败告终。
2. 采用tcpdump抓包查看
在服务器端添加了日志,然后用“服务器A_IP :port”作为请求URL的时候,日志中的Parameters如下所示:
当用“server_name” 作为请求URL的时候,日志中的Parameters如下所示:
[["f78494dc412a0455a5b67f68707b2b97",null]]
很明显,用“server_name” 作为请求URL的请求参数中cookie和session都是空的,这也难怪会被403,给拒绝掉。
但是我的测试脚本中明明是有Set-Cookie的。为了一探究竟,用tcpdump进行抓包:
可以发现cookie中的参数是我在脚本中set进去的,登录获得的patsid。但是却被服务器拒绝掉了。
3. 终于查到根源
咨询了nginx大牛——刘成同学,终于找到了nginx的403问题的根源了,问题就在出现在请求参数的header中的这个参数'authenticity_token'。
因为nginx中的自身很多变量都是以下划线标识的,所以遇到这个'authenticity_token'的时候,nginx为了防止混淆,他会把用户发过来的过滤掉,所以就识别不到这个token值。解决方法是在nginx的配置的http模块中添加 underscores_in_headers on;(这个默认值是关闭的) 然后重启nginx之后,就可以用脚本返回正常的请求结果了。
HTTP头部是否允许下划线
语法:underscores_in_headers on | off;
默认:underscores_in_headers off;
配置块:http、server
默认为off,表示HTTP头部的名称中不允许带“_”(下划线)。
可以查看ngx_http_core_module 模块的说明。
至此,一个由nginx引发的403问题终于解决掉了,希望能给大家以帮助。