背景描述
問題
-
阿里雲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採用相應的協議。