共计 3661 个字符,预计需要花费 10 分钟才能阅读完成。
美妙周末,从解 BUG 开始!
周五原本想早点上班,临了有一个简略的需要忽然提上来,心想着整完了就走,没想到一下折腾了 1 个多小时才搞定,欢快的周末就从加班中开启了。回到家里把这件事复盘一下,小伙伴们看看是否可能从中 GET 到一些未知的货色。
需要是这样的:
我的项目是 Spring Boot
我的项目,里边对申请进行了划分,有的申请是 HTTP
协定,有的申请是 HTTPS
协定,我的项目规定,有一些申请必须是 HTTPS
协定,例如 /https 接口,该接口必须应用 HTTPS
协定拜访,如果用户应用了 HTTP
协定拜访,那么会主动产生申请重定向,重定向到 HTTPS
协定上;同时也有一些申请必须是 HTTP
协定,例如 /http 接口,该接口必须应用 HTTP
协定拜访,如果用户应用了 HTTPS
协定拜访,那么会主动产生申请重定向,重定向到 HTTP
协定上。对于一些没有明确规定的接口,当用户拜访 HTTP
协定时,不须要主动跳转到 HTTPS
协定上,即用户如果应用 HTTP
协定就是 HTTP
协定,用户如果应用 HTTPS
协定就是 HTTPS
协定。
这个工作切实是小 case,因为我的项目自身曾经反对 HTTPS
了,我只须要再增加一个 HTTP
监听的端口即可 (Spring Boot 中配置 Https),增加如下配置:
@Configuration
public class TomcatConfig {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory() {TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addAdditionalTomcatConnectors(createTomcatConnector());
return factory;
}
private Connector createTomcatConnector() {
Connector connector = new
Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080);
return connector;
}
}
增加实现后,我的项目启动日志如下:
能够看到,我的项目曾经同时反对 HTTPS
和 HTTP
了,两者别离在不同的端口上监听。
接下来利用 Spring Security 中的 HTTPS
校验转发性能对申请进行辨别:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
// 省略其余
.requiresChannel()
.antMatchers("/https").requiresSecure()
.antMatchers("/http").requiresInsecure()
.and()
.csrf().disable();
}
}
功败垂成,so easy!
配置实现后,启动我的项目,如下两个地址都能够拜访到登录页面:
- http://localhost:8080/login
- https://localhost:8444/login
能够应用任意一个地址登录。
如果应用了 HTTP
协定登录,登录胜利后,如果间接拜访 http://localhost:8080/http
申请,能够间接拜访到,没有任何问题;如果登录胜利后拜访 http://localhost:8080/https
申请,则会主动重定向到 https://localhost:8444/https
,所有看起来都很完满。
仿佛能够上班了。
别急,我再用 HTTPS
登录测试了,关上 https://localhost:8444/login
页面,登录胜利,申请 https://localhost:8444/https
地址没有问题,申请 https://localhost:8444/http
,傻眼了。
当我应用 HTTPS
登录胜利后,申请 https://localhost:8444/http
地址时,按理说会重定向到 http://localhost:8080/http
,后果并没有,而是重定向到登录页面,这是咋回事?更为诡异的是,当初在登录页面,无论我怎么做,都登录失败。
看来 965 到底是空中楼阁,还是持续解决问题吧。
那就从登录开始,好端端的为什么忽然就无奈登录了呢?
先革除浏览器缓存试试?咦,革除浏览器缓存后登录胜利了!
通过屡次尝试后,我总结进去了如下法则:
如果应用 HTTP
协定登录,登录胜利后,HTTP 协定和 HTTPS
协定之间相互重定向没有任何问题。如果应用了 HTTPS
协定登录,登录胜利后,HTTPS 协定重定向到 HTTP
协定时,须要从新登录,并且在登录页面总是登录失败,须要革除浏览器缓存能力登录胜利。
先找到法则这个很重要,有的小伙伴微信问松哥问题时候,喜爱说,
这个货色它一会能够一会又不行
,诚实说,这个问题提的十分业余!所有看似无规律的 BUG 背地都是有法则的,找到法则才是解决 BUG 的第一步。
在整个过程中,最为诡异的是从 HTTPS
重定向到 HTTP
之后,无论怎么样都登录不了,服务端重启也没用,只能革除浏览器缓存,这个十分奇怪,我感觉就先从这个中央动手 DEBUG。
那就 DEBUG,浏览器发送登录申请,服务端我把 Spring Security
登录流程走了一遍,貌似没问题,登录胜利后重定向到 http://localhost:8080/,这也是失常的,持续 DEBUG,重定向到 http://localhost:8080/ 地址时,呈现了一点点意外,该申请在 Spring Security 过滤器链的最初一个环节 FilterSecurityInterceptor 中执行时候抛出异样了,异样起因是因为检查用户身份,发现这是个匿名用户!(一文搞定 Spring Security 异样解决机制!)
不对呀,一开始曾经登录胜利了,怎么会是匿名用户呢?Spring Security
在登录胜利后,会将用户信息保留在 SecurityContextHolder
中(在 Spring Security 中,我就想从子线程获取用户登录信息,怎么办?),是不是没保留?从新查看登录过程,发现登录胜利后是保留了用户信息的。然而当登录胜利后再次发送申请却说我没登录,还剩一种可能,是不是前端申请的问题,JSESSIONID 拿错了?或者没拿?
浏览器 F12 查看前端申请,发现登录胜利后,重定向到 http://localhost:8080/
地址时,果然没有携带 Cookie!
当初的问题是为什么它就不携带 Cookie 呢?
一瞬间脑子里闪过了诸多可能性,是不是浏览器 SameSite 机制导致的?是不是。。。最初思维定格在 Cookie 的 Secure 标记上。
如果申请是 HTTPS,则服务端响应的 Cookie 中含有 Secure 标记:
这个标记示意该 Cookie 只能够在平安环境下(HTTPS)传输,如果申请是 HTTP
协定,则不会携带该 Cookie。这样就能解释通为什么登录胜利后重定向时不携带 Cookie 了。
新的问题来了,我应用的是 HTTP
协定登录,为什么 Cookie 中有 Secure 标记呢?答复这个问题,咱们要残缺的梳理一遍登录过程。
首先咱们应用 HTTPS
协定登录,登录胜利后,返回的 Cookie 中含有 Secure 标记,接下来咱们拜访 https://localhost:8444/http
,该申请重定向到 http://localhost:8080/http
,重定向的申请是 HTTP
申请,而 Cookie 只能够在 HTTPS
环境下传输,所以不会携带 Cookie,服务端认为这是一个匿名申请,所以要求重定向到登录页面,回到登录页面持续登录,此时发动的登录是 HTTP
申请,即端口是 8080,因为 Cookie 并不会辨别端口号,所以应用 8080 登录胜利后,应用的还是之前 8444 生成的 Cookie,然而 8080 又无奈在发送申请时,主动携带该 Cookie,所以看到的就是总是登录失败,当革除浏览器缓存后,8444 的 Cookie 就被革除了,8080 再次登录就能够生成本人的没有 Secure 标记的 Cookie,此时所有又恢复正常了。
这里边其实次要波及到两个知识点:
- 含有 Secure 标记的 Cookie 只能够在平安环境下(HTTPS)传输。
- Cookie 是不辨别端口号的,如果 Cookie 名雷同,会主动笼罩,并且读取的是雷同的数据。所以 8080 和 8444 并不会主动应用两个 Cookie。
至此,总算搞清楚这个诡异的登录问题了。那么接下来的解决方案就很容易了。
还是那句话,所有看似无规律的 BUG 都是有法则的,找到法则才有解决问题的可能性!