一个HTTPS转HTTP的Bug,他们忍了2年,原谅我无法接受,加班改了

简介: 一个HTTPS转HTTP的Bug,他们忍了2年,原谅我无法接受,加班改了

今天这篇文章给大家讲一个追查Bug的故事和过程。个人一直认为:事出反常必有妖,程序中的Bug也是如此。


希望通过这个Bug的排查故事,大家不仅能够学到一系列的知识点,同时也能学会如何解决问题,如何更加专业的做事。而解决问题的方式及思维比单纯的技术更加重要。


Let’s go!


故事的起因

刚接手新团队新项目没多久,在发布一个系统时,同事友善的提醒:发布xx系统时,在测试环境要注释掉一行代码,上线发布时再放开注释。


听此友善提醒,一惊:这又是什么黑科技啊?!在我的经验里,还没有什么系统需要这样处理,暗下决心要排查此问题。


终于抽出时间,周五折腾了多半天,没解决掉,周末还心里惦记着,于是加班也搞定这个问题。


Bug的存在及操作

项目是基于JSP的,没有做前后端分离。在JSP页面中引入了一个公共的head.jsp,该文件内有这样一行代码和注释:



同事友善提醒的就是注释上的操作,测试环境注释掉(不然无法访问),生产环境需要放开,不然也无法访问(转圈圈啊)。据注释说明,大概知道是用来解决HTTPS相关的问题。


那么,是什么原因导致了要这样操作?有没有更简单的操作?大家只是在这么做,没人寻找问题的根源,也没人能出答案,只能自己去寻找了。


HTTPS中的HTTP请求

先来看看配置META元素是干什么用的。


其中http-equiv指定的“Content-Security-Policy”就"网页安全政策",缩写CSP,常用来防止XSS攻击。


通常的使用方法就是在HTML中通过meta标签来进行定义:



其中,在content中可以指定涉及安全的各类限制策略。


项目中使用的upgrade-insecure-requests便是限制策略之一,作用是:自动将网页上所有加载外部资源的HTTP链接换成HTTPS协议。


此刻稍微明白了一点,原来最初写这行代码是想将HTTP请求强制转换成HTTPS请求啊。


但正常情况来说,只要在Nginx或SLB中配置了HTTP转HTTPS便不会出现这类问题,而系统中是有对应的配置的。


于是,在线上另起一个服务实验了一下,注释掉这段代码,部分功能还真的在转圈圈,诚不欺我!


为什么HTTPS中不允许HTTP请求

查看浏览器中的请求,发现转圈圈原来是如下错误引起的:


Mixed Content: The page at 'https://example.com' was loaded over HTTPS, but requested an insecure stylesheet 'http://example.com/xxx'. This request has been blocked; the content must be served over HTTPS.


其中,Mixed Content即混合内容。所谓的混合内容通常出现在以下情况:初始的HTML的内容是通过HTTPS加载的,但其他资源(比如,css样式、js、图片等)则通过不安全的HTTP请求加载。此时,同一个页面,同时使用了HTTP和HTTPS的内容,而HTTP协议会降低整个页面的安全性。


因此,现代浏览器会针对HTTPS中的HTTP请求进行警告,阻断请求,并抛出上述异常信息。


现在,问题的原因基本明确了:HTTPS请求中出现了HTTP请求。


那么,解决方案有几种:


方案一:在HTML中添加meta标签,强制将HTTP请求转换成HTTPS请求。这也是上面的使用方式,但这种方式的弊端也很明显,在没有使用HTTPS的测试环境,需要手动的注释掉。否则,也无法正常访问。

方案二:通过Nginx或SLB的配置,将HTTP请求转换成HTTPS请求。

方案三:最笨的方法,找到项目中存在HTTP请求的问题,逐个修复。

初步改造,略显成效

目前使用的第一种方案很显然不符合要求,而第二种方案已经配置了,但部分页面依旧不起效。那么,还有其他方案吗?


经过大量排查,发现导致不起效的原因是:项目中大量使用了redirect方式的跳转。


@RequestMapping(value = "delete")

public String delete(RedirectAttributes redirectAttributes) {

 //.. do something

 addMessage(redirectAttributes, "删除xxx成功");

 return "redirect:" + Global.getAdminPath() + "/list";

}


redirect方式的跳转在HTTPS的环境下会重定向到HTTP协议,导致无法访问。


这也太坑了,难怪上面HTTP转HTTPS的设置都配置完成了,部分页面还不起效。


而导致这个问题的根本原因是Spring的ViewResolver对HTTP 1.0协议的兼容。


针对此问题,将其关闭即可解决,具体改造方案有两个。


方案一,将redirect改为RedirectView类来实现:


modelAndView.setView(new RedirectView(Global.getAdminPath() + "/list", true, false));

1

其中RedirectView的最后一个参数设置为false,就是将http10Compatible的开关关闭,不对HTTP 1.0协议进行兼容。


方案二:配置Spring的ViewResolver的redirectHttp10Compatible属性。通过这种方案,可以实现全局关闭。


 

 

 

 


由于项目中使用redirect较多,于是就采用了第二种方案。修改之后,发现大部分问题都解决了。


为了防止遗漏,就多点了一些页面,竟然还有漏网之鱼!


Shiro拦截器又作祟

解决了重定向导致的问题,以为万事大吉了,结果涉及到Shiro重定向的页面又出现了类似的问题。原因很简单:某些页面的权限验证需要经过Shiro,但Shiro将HTTPS请求拦截之后,重定向时转换成了HTTP请求。


那么,为什么视图层将redirectHttp10Compatible设置为false不起效呢?


追踪了Shiro拦截器中的代码,发现Shiro在拦截器中默认将redirectHttp10Compatible设置为true,又是一坑~


查看源码可以发现,Shiro的登录过滤器FormAuthenticationFilter的方法中调用了saveRequestAndRedirectToLogin方法:


protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    saveRequest(request);
    redirectToLogin(request, response);
}
// 进而调用redirectToLogin方法
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
   String loginUrl = getLoginUrl();
   WebUtils.issueRedirect(request, response, loginUrl);
}
// 通过WebUtils.issueRedirect进行设置
public static void issueRedirect(ServletRequest request, ServletResponse response, String url) throws IOException {
    issueRedirect(request, response, url, (Map)null, true, true);
}
// 通过WebUtils.issueRedirect重载方法
public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
    RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
    view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
}

通过上述代码追踪,可以看到,最终在WebUtils的issueRedirect方法中调用了两次issueRedirect,而http10Compatible参数值默认为true。

找到问题的根源,解决起来就简单了,重写FormAuthenticationFilter拦截器:

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
      if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        redirectToLogin(request, response);
    }
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        String loginUrl = getLoginUrl();
        WebUtils.issueRedirect(request, response, loginUrl, null, true, false);
    }
}

示例中,将onAccessDenied中需要原本调用WebUtils.issueRedirect方法的http10Compatible参数改为false即可。


上面只是示例,实际上不仅包括成功页面,还包括失败页面等,都需要重新实现一下对应的方法。最后,在shiroFilter中配置自定义的拦截器。


  <!-- 自定义的登录过滤器-->
  <bean id="customFilter" class="com.senzhuang.shiro.CustomFormAuthenticationFilter" />
  <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="/login.html"></property>
    <property name="unauthorizedUrl" value="/refuse.html"></property>
    <property name="filters">
        <map>
            <entry key="authc" value-ref="customFilter"/>
        </map>
      </property>
  </bean>

经过上述的改造,关于HTTPS中的HTTP请求问题已经得到解决了。


为了防止遗漏,又挨个点了一些页面,又发了问题了!哎,咋那么手欠呢……


LayUI的坑

本来以为解决了上面的问题,就彻底解决了,可以吃顿烧烤庆祝一下了。结果,在前端页面中又发现了类似的错误。但此时错误信息来自访问登录页面的路径:


http://example.com/a/login

1

奇了怪了,已经登录成功了,为什么业务操作页面还会再请求login页面呢?而且跳转过去还是HTTP请求,而不是HTTPS的请求。


查看了一下login的请求结果:


image.png

排查了相关的业务代码,登录完成之后,再也没有请求登录请求了啊,为什么会再次请求一次login呢?难道是访问某些资源受限,导致重定向到登录页面了?

于是,查看了一下HTML调用的”Initiator“:

image.png原来是LayUI请求对应的layer.css资源时,触发了login的登录操作。


首先想到的是Shiro中没有放开静态资源的拦截,于是在Shiro中放开了layui的拦截权限,但问题已经存在。


再次排查,发现页面中没有主动引入layer.css文件,于是主动引入了layer.css文件,但问题还是存在。


没办法,只好查看layui.js,看看为什么要发起这个请求。此时,还留意到请求路径中有一个"undefinedcss"的词。


用过js的朋友都知道,undefined是js中变量未初始化的默认值,类似Java中的null。


在layui.js中搜索”css/“,还真找到这样一段代码:


return layui.link(o.dir + "css/" + e, t, n)

1

对照起来,也就是说o.dir的值为"undefined",与后面的css连接起来就变成了"undefinedcss",而这个路径并不存在,也没在Shiro中进行权限配置,默认会走到登录界面去。而这里是内部的一个异步的redirect请求,不会在页面呈现,要查看浏览器的错误信息才能发现。


找到问题原因了,改造起来就简单了,将layui的link方法参数进行修改:


// 注释掉

// return layui.link(o.dir + "css/" + e, t, n)


// 改为

return layui.link((o.dir ? o.dir:"/static/sc_layui/") +"css/"+e, t, n)

1

2

3

4

5

改造的基本思路是:如果o.dir有值(js中有值即为true)则使用o.dir的值;如果o.dir为undefined则采用指定的默认值。


其中"/static/sc_layui/"为项目中存放layui组件的路径。由于layui.js可能是压缩后的js,可通过搜索”css/“或”layui.link“找到对应的代码。


重启项目,清除浏览器缓存,再次访问页面,问题得到彻底解决。


可以安心吃烤串了

周末又花了半天时间,终于把这个问题彻底解决了,现在可以安心去吃顿烤串庆祝一下了。


最后,回顾一下这个过程,看看你能从中收获到什么:


出现问题:不同环境(HTTP和HTTPS)需要手动改代码;

寻找问题:为了安全,HTTPS内不允许发起HTTP请求;

解决问题:两种方式关闭http10Compatible;

Shiro问题:Shiro中默认为关闭http10Compatible,重写Filter,实现关闭操作;

LayUI Bug修复:LayUI代码bug,导致发起http(登录)请求。修复此Bug;

在这个过程中,如果你只是安于现状,”遵守规则“,每次上线时修改一下文件,不仅费时费力,而且不知为什么要这么做。


但如果像笔者一样,刨根问底的追踪一下,你将会学到一系列的知识:


HTTP请求的CSP,upgrade-insecure-requests配置;

HTTPS中为什么不能发起HTTP请求;

Spring视图解析器中配置http10Compatible;

redirect方式视图返回的弊端;

Nginx中如何将HTTP请求转为HTTPS请求;

HTTP请求的混合内容(Mixed Content)概念及错误;

HTTP 1.0、HTTP 1.1、HTTP2.0协议的区别;

Shiro拦截器自定义Filter;

Shiro拦截器过滤指定URL访问;

Shiro拦截器的配置及部分源码实现;

LayUI的一个bug;

其他排查该问题时用到或学到的技术;

这些技术你学到了吗?解决问题的思路和方式方法你学到了吗?如果本文有那么一点内容启发到你了,我不吝分享,你也不要吝啬,点个赞吧。



目录
相关文章
|
3天前
|
前端开发 JavaScript 数据库
https页面加载http资源的解决方法
https页面加载http资源的解决方法
13 7
|
17天前
|
安全 应用服务中间件 网络安全
简单比较 http https http2,我们要如何把http升级为https
【9月更文挑战第13天】本文对比了HTTP、HTTPS和HTTP/2的特点与适用场景。HTTP以明文传输,适合低安全要求的环境;HTTPS通过SSL/TLS加密,适用于电子商务等安全要求高的场景;HTTP/2采用二进制格式和多路复用,适合高性能Web应用。文章还详细介绍了将HTTP升级为HTTPS的步骤,包括申请和安装SSL证书、配置Web服务器、重定向HTTP流量到HTTPS以及测试HTTPS功能。升级到HTTPS可提高数据安全性和用户信任度。
51 13
|
18天前
|
安全 网络安全 数据安全/隐私保护
HTTP与HTTPS协议区别及应用场景
在互联网高速发展的今天,HTTP与HTTPS作为数据传输的基石,作用至关重要。HTTP允许客户端与服务器间传输超文本文档,但其数据传输过程未加密,存在安全隐患;HTTPS则在此基础上加入了SSL/TLS协议,实现了数据加密传输,增强了安全性,广泛应用于电子商务、网上银行、政府网站及社交媒体平台等涉及敏感信息传输的领域,有效保护了用户隐私和数据安全。随着网络安全意识提升,HTTPS正逐渐成为主流。
|
3月前
|
JSON 网络协议 安全
《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(1)-HTTP和HTTPS基础知识
【7月更文挑战第16天】本文介绍了HTTP和HTTPS协议的基本概念与作用,强调了理解HTTP协议对使用抓包工具Fiddler的重要性。HTTP是用于Web浏览器与服务器间信息传输的协议,不加密,易被截取,不适合传输敏感信息。HTTPS是HTTP的安全版,通过SSL/TLS提供加密和服务器身份验证,确保数据安全。HTTP请求包括请求行、请求头、空行和可选的请求主体,响应则有响应行、响应头、空行和响应主体。HTTP协议无状态,而HTTPS解决了安全性问题,但也带来了额外的计算开销。Fiddler作为一个强大的抓包工具,可以帮助开发者和测试人员分析HTTP/HTTPS通信,理解请求和响应的结构。
59 4
《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(1)-HTTP和HTTPS基础知识
|
3月前
|
缓存 网络协议 算法
(二)Java网络编程之爆肝HTTP、HTTPS、TLS协议及对称与非对称加密原理!
作为一名程序员,尤其是Java程序员,那必须得了解并掌握HTTP/HTTPS相关知识。因为在如今计算机网络通信中,HTTP协议的作用功不可没,无论是日常上网追剧、冲���、亦或是接口开发、调用等,必然存在HTTP的“影子”在内。尤其对于WEB开发者而言,HTTP几乎是每天会打交道的东西。
69 10
|
2月前
|
Linux Python
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
|
2月前
|
JavaScript 前端开发 Java
【Azure 环境】各种语言版本或命令,发送HTTP/HTTPS的请求合集
【Azure 环境】各种语言版本或命令,发送HTTP/HTTPS的请求合集
|
3月前
|
安全 程序员 网络安全
HTTP和HTTPS的区别,你真的了解吗?
大家好,我是你们的技术小伙伴小米!今天我们来聊聊HTTP和HTTPS的区别以及HTTPS链接的建立过程,同时了解两种常见的加密算法——对称加密和非对称加密。通过这篇文章,你将深入理解这些网络基础知识,为网站安全保驾护航!
58 7
|
2月前
|
安全 网络协议 搜索推荐
http和https分别是什么?区别是什么?
http和https分别是什么?区别是什么?
73 0
|
3月前
|
网络协议 网络安全 数据安全/隐私保护
HTTPS与HTTP的一些区别
HTTPS与HTTP的一些区别
下一篇
无影云桌面