Https跳到http時session資訊丟失的分析及解決方案
部落格分類:
Tomcat瀏覽器WebServlet
我們在YMU(website monitoring)項目開發過程中發現一個關於登入功能的奇怪的問題。
當按一般流程使用登入功能時是沒問題的,即:點擊官網 (http://YouMonitor.Us)的login連結,然後跳轉到https://YouMonitor.Us/login.shtml,輸入正確的使用者名稱和密碼後,則能正確轉入功能頁面(http協議)。
而如果跳過第一步,直接在瀏覽器中輸https://YouMonitor.Us/login.shtml,則不能正確轉入功能頁面。
原因分析
經調試發現是由於session造成的。當使用者名稱和密碼通過驗證後,YMU會在session中儲存登入使用者名稱。
在第二種方式下,使用者名稱被儲存在https下建立的session中,而不能被傳遞到http協議,這樣當以http協議跳轉到功能頁面時,發現session中沒有使用者名稱,系統就會認為沒有登入,就出問題了。
再深入分析session的傳遞機制,其中一種方式是通過JSESSIONID這個cookie在瀏覽器和web server之間進行傳遞的。而為了增強安全性,從tomcat 4.0開始,在https協議下產生的cookie不會被傳遞到http協議。
這就是登入問題產生的根本原因。
解決方案
前一段時間在網上找到一種解決方案,該方法修改tomcat的原始碼,以解除cookie傳遞的限制。該方法的缺點是每次tomcat升級都要重新修改並編譯,很不方便。
還有沒有更方便的方法呢?上周公司一同事在網上又找到一個更加簡潔和方面的方法。其主要思路是建立一個filter, 對所有的Servlet Request做處理,如果session是新的https session,則建立一個JSESSIONID cookie,並設定到Servlet Response中,這樣就突破了tomcat的限制,把https下的session傳遞到http協議下。
根據文章說明試了一下,果然成功了!這下解決了系統的一個BUG,不錯啊!
附:原始碼
1. 先建立Wrapper類,用於處理所有的Serverlet Request:
public class MyRequestWrapper extends HttpServletRequestWrapper{
private HttpServletResponse response = null;
public MyRequestWrapper(HttpServletRequest request) {
super(request);
}
public void setResponse(HttpServletResponse response) { this.response = response;}
public HttpSession getSession(){
HttpSession session = super.getSession();
processSessionCookie(session);
return session;
}
public HttpSession getSession(boolean create){
HttpSession session = super.getSession(create);
processSessionCookie(session);
return session;
}
private void processSessionCookie(HttpSession session){
if (null == response || null == session) {
// No response or session object attached, skip the pre processing
return;
}
// cookieOverWritten - 用於過濾多個Set-Cookie頭的標誌
Object cookieOverWritten = getAttribute("COOKIE_OVERWRITTEN_FLAG");
if (null == cookieOverWritten && isSecure() && isRequestedSessionIdFromCookie() && session.isNew()) {
// 當是https協議,且新session時,建立JSESSIONID cookie以欺騙瀏覽器
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(-1); // 有效時間為瀏覽器開啟或逾時
String contextPath = getContextPath();
if ((contextPath != null) && (contextPath.length() > 0)) {
cookie.setPath(contextPath);
}
else {
cookie.setPath("/");
}
response.addCookie(cookie); // 增加一個Set-Cookie頭到response
setAttribute("COOKIE_OVERWRITTEN_FLAG", "true");// 過濾多個Set-Cookie頭的標誌
}
}
}
2. 再把上述Wrapper類與Filter建立關聯:
public final class TestFilter implements Filter{
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
MyRequestWrapper myrequest = new MyRequestWrapper(request);
myrequest.setResponse(response);
chain.doFilter(myrequest, response);
}
}
http://java-guru.iteye.com/blog/157897