背景描述
问题
-
阿里云slb配置443端口监听,然后将80端口的监听配置为重定向到https:443端口。
-
通过http://abc.com来访问站点,成功跳转至https://abc.com,实现了http强制跳https。
-
输入账号、密码登录系统,然后 “500”错误,F12浏览器debug发现提示以下错误:
Mixed Content: The page at 'https://abc.com' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://abc.com/onLoginSuccess.html'. This request has been blocked; the content must be served over HTTPS.
部署结构
网上搜索到的方案
方案一
Spring MVC 里面使用到了redirect:/path
,可以通过以下配置来搞定:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- redirectHttp10Compatible:解决https环境下使用redirect重定向地址变为http的协议,无法访问服务的问题,设置为false,即关闭了对http1.0协议的兼容支持-->
<property name="redirectHttp10Compatible" value="false" />
</bean>
大概意思就是因为代码了做了对http1.0
兼容,导致了重定向以后返回去的就是http了。
方案二
使用了shiro
框架,里面有什么loginUrl
、sucessUrl
之类的配置,这里也会利用redirect进行跳转,具体方案如下:
- 重写RedirectView类,将
http10Compatible
开关关闭; - 硬编码强制修改response Header里面的Location的值;
太复杂了,学不会呀!!!
原理剖析
Servlet容器重定向
Servlet容器Tomcat(tomcat-embed-8.5.31)里面的一个类:org.apache.catalina.connector.Response
里面有这么一段代码:
/**
* 如果有需要的话把一个相对地址转化为绝对地址(我自己乱翻译的)
*/
protected String toAbsolute(String location) {
...
boolean leadingSlash = location.startsWith("/");
if (location.startsWith("//")) {
// Add the scheme
String scheme = request.getScheme();
....
} else if (leadingSlash || !UriUtil.hasScheme(location)) {
String scheme = request.getScheme();
...
} else {
//啥也不干 直接返回
return (location);
}
}
意思就是,需要重定向的话,Location里面的地址的Scheme根据request来,两者保持一致。
Shiro 重定向
属性http10Compatible
的值影响重定向的行为。
org.apache.shiro.web.util.RedirectView
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException {
if (http10Compatible) {
response.sendRedirect(response.encodeRedirectURL(targetUrl));
} else {
response.setStatus(303);
response.setHeader("Location", response.encodeRedirectURL(targetUrl));
}
}
如果http10Compatible=true
,就把重定向的targetUrl的组装工作交给了servlet 容器处理,容器肯定是按照Servlet规范做了。
如果http10Compatible=false
,响应码是303
,并且Location
的值是一个相对地址。猜测是浏览器收到这个相对地址以后会自动拼接前面的http(s)://abc.com
,然后发起重定向。
Spring MVC 重定向
Spring MVC 中控制对Http 1.0 是否兼容的标识位是redirectHttp10Compatible
该属性在UrlBasedViewResolver
视图解析器内。
生效的地方依然是RedirectView
,类:org.springframework.web.servlet.view.RedirectView
相关代码:
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
...
//encodedURL /abc/tests/safa.html
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
response.setHeader("Location", encodedURL);
}
else {
// Send status code 302 by default.
response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
//303
response.setStatus(statusCode.value());
response.setHeader("Location", encodedURL);
}
}
如果redirectHttp10Compatible=true
,就把重定向的targetUrl的组装工作交给了servlet 容器处理,容器肯定是按照Servlet规范做了。
如果redirectHttp10Compatible=false
,响应码是303
,并且Location
的值是一个相对地址。猜测是浏览器收到这个相对地址以后会自动拼接前面的http(s)://abc.com
,然后发起重定向。
总结
到这里问题基本清楚了,来个总结吧。
Http10Compatible | Http10 No Compatible |
---|---|
Status Code :200Location :"${request.scheme}:\//domain.com/targetPath.html" | Status Code :303Location :"/targetPath.html" |
结论很明显Http10Compatible
兼容与否,其实跟Http、Https并没有关系,只是恰巧出现的303状态码,以及Location里面存放的不在是完整的跳转url,而是一个/
开头的相对地址,协议的组装交给了浏览器处理。因此给大家了这种错觉,Https和Http的问题依然存在。
最佳实践
Tomcat有一个配置项:
public static class Tomcat {
/**
* Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
*/
private String protocolHeader;
}
当请求通过nginx代理或者阿里云SLB转发的时候通过配置将请求protocol
放在X-Forwarded-Proto
Header 里面。后端容器就会根据协议在生成redirect Location采用相应的协议。