專案需求:Session 失效後,普通請求重導向至登入頁;AJAX 請求回傳特定標頭,由前端統一攔截彈窗並跳轉。嘗試 HttpSessionListener 無法實現頁面跳轉,最終採用 Filter 方案。
<filter>
<filter-name>sessionFilter</filter-name>
<filter-class>com.example.filter.SessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionFilter</filter-name>
<url-pattern>/actions/*</url-pattern> <!-- 僅攔截業務路徑 -->
</filter-mapping>
public class SessionFilter implements Filter {
private static final String LOGIN_FLAG = "LOGIN_SUCCESS";
private static final String LOGIN_URL = "/admin/login.jsp";
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession(false); // 不自動建立
String requestURI = request.getRequestURI();
String path = requestURI.substring(requestURI.lastIndexOf("/"));
// 僅攔截 .action 結尾的業務請求
if (path.endsWith(".action") && (session == null || session.getAttribute(LOGIN_FLAG) == null)) {
// 判斷是否 AJAX 請求
String requestedWith = request.getHeader("X-Requested-With");
boolean isAjax = "XMLHttpRequest".equalsIgnoreCase(requestedWith);
String loginPath = request.getContextPath() + LOGIN_URL;
if (isAjax) {
// AJAX:回傳自定標頭,不輸出頁面
response.addHeader("sessionstatus", "timeOut");
response.addHeader("loginPath", loginPath);
chain.doFilter(request, response); // 讓後續流程結束,避免進一步處理
} else {
// 普通請求:輸出 JS 彈窗並重導向頂層視窗
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>alert('會話已過期,請重新登入'); window.top.location.href='" + loginPath + "';</script>");
out.flush();
}
return;
}
chain.doFilter(request, response);
}
@Override public void init(FilterConfig config) {}
@Override public void destroy() {}
}
$(document).ajaxComplete(function(event, xhr, settings) {
if (xhr.getResponseHeader("sessionstatus") === "timeOut") {
var loginPath = xhr.getResponseHeader("loginPath");
if (loginPath) {
alert("會話已過期,請重新登入");
window.location.replace(loginPath);
} else {
alert("請求超時,請重新登入");
}
}
});
Ext.Ajax.on('requestcomplete', function(conn, response, options) {
if (response.getResponseHeader("sessionstatus") === "timeout") {
var loginPath = response.getResponseHeader("loginPath");
if (loginPath) {
alert("會話已過期,請重新登入");
window.top.location.href = loginPath;
} else {
alert("請求超時,請重新登入");
}
}
});
$.ajax({
url: "test.html",
global: false // 不觸發 ajaxComplete 等全域事件
});
getSession(false) 避免 Filter 本身創建新 Session 導致判斷失效。X-Requested-With: XMLHttpRequest 標頭識別 AJAX(jQuery/ExtJS 預設發送)。sessionstatus=timeOut + loginPath,前端零耦合業務代碼。<script> 強制頂層視窗跳轉,適用於 iframe 嵌套場景。此方案已在多個專案穩定運行,兼容傳統表單提交與現代 AJAX 架構。