Spring MVC HandlerInterceptor 可在 Controller 執行前/後/完成後攔截請求,是實現登入驗證、權限檢查、日誌記錄的標準方式。本文完整示範登入攔截器實作與配置。
| 方法 | 執行時機 | 返回值 | 用途 |
|---|---|---|---|
| preHandle | Controller 執行前 | boolean | 驗證、攔截、重定向;false 中斷後續流程 |
| postHandle | Controller 執行後、視圖渲染前 | void | 修改 Model、統一視圖數據 |
| afterCompletion | 視圖渲染完成後 | void | 資源清理、耗時統計、異常處理 |
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final String LOGIN_URL = "/loginpage.html";
private static final String USER_SESSION_KEY = "currentUser";
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
HttpSession session = req.getSession(false); // 不自動建立
if (session == null || session.getAttribute(USER_SESSION_KEY) == null) {
// 未登入:重定向到登入頁
resp.sendRedirect(req.getContextPath() + LOGIN_URL);
return false; // 關鍵:阻止進入 Controller
}
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView mv) {
// 可統一注入選單、用戶資訊等
HttpSession session = req.getSession(false);
if (session != null && mv != null) {
mv.addObject("loginUser", session.getAttribute(USER_SESSION_KEY));
}
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) {
// 記錄請求耗時、清理 ThreadLocal 等
if (ex != null) {
log.error("請求異常", ex);
}
}
}
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/user/**") // 攔截用戶中心相關
.excludePathPatterns("/loginpage.html", "/doLogin", "/static/**"); // 排除登入頁、靜態資源
}
}
或 XML 配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<mvc:exclude-mapping path="/loginpage.html"/>
<mvc:exclude-mapping path="/doLogin"/>
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
@Controller
public class UserController {
@PostMapping("/doLogin")
public String login(@RequestParam String username, @RequestParam String password,
HttpSession session, RedirectAttributes ra) {
User user = userService.authenticate(username, password);
if (user != null) {
session.setAttribute("currentUser", user);
return "redirect:/user/home";
}
ra.addFlashAttribute("error", "帳號或密碼錯誤");
return "redirect:/loginpage.html";
}
@GetMapping("/user/home")
public String home() {
return "user/home"; // 只有登入用戶能進入
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/";
}
}
| 項目 | 建議 |
|---|---|
| 排除靜態資源 | 務必 excludePathPatterns("/static/**", "/css/**", "/js/**"),否則圖片/CSS 也會被攔截導致 404 |
| session.getSession(false) | 不自動建立 Session,避免匿名用戶產生大量無效 Session |
| 重定向 vs 轉發 | 攔截器中用 sendRedirect(重定向),瀏覽器地址欄變為登入頁 URL |
| Ajax 請求處理 | 檢測 X-Requested-With: XMLHttpRequest,返回 401 JSON 而非重定向 HTML |
| 多攔截器順序 | 按 addInterceptor 順序執行;登入驗證通常放第一個 |
| ThreadLocal 清理 | 在 afterCompletion 清理請求級上下文,防記憶體洩漏 |
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireLogin {}
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
if (handler instanceof HandlerMethod hm) {
RequireLogin ann = hm.getMethodAnnotation(RequireLogin.class);
if (ann != null && !isLoggedIn(req)) {
sendUnauthorized(resp);
return false;
}
}
return true;
}
}
Controller 方法加 @RequireLogin 即可單獨保護,無需全域路徑匹配。
HandlerInterceptor 是 Spring MVC 處理橫切關注點的核心機制。登入驗證核心邏輯:preHandle 檢查 Session → 未登入 sendRedirect + return false。配合 excludePathPatterns 排除公開路徑,並在 postHandle/afterCompletion 處理共享數據與清理。