阿裡雲slb配置https重定向後變http問題解決
- 背景描述
-
- 問題
- 部署結構
- 網上搜尋到的方案
-
-
- 方案一
- 方案二
-
- 原理剖析
-
- Servlet容器重定向
- Shiro 重定向
- Spring MVC 重定向
- 總結
- 最佳實踐
背景描述
問題
- 阿裡雲slb配置443端口監聽,然後将80端口的監聽配置為重定向到https:443端口。
- 通過http://abc.com來通路站點,成功跳轉至https://abc.com,實作了http強制跳https。
- 輸入賬号、密碼登入系統,然後 “500”錯誤,F12浏覽器debug發現提示以下錯誤:
部署結構
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csc3aUJGc1cFZwhnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwUzM2IjMxgTMyADNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
網上搜尋到的方案
方案一
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 |
---|---|
:200 :"${request.scheme}:\//domain.com/targetPath.html" | :303 :"/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采用相應的協定。