使用SpringSecurity下,發生空轉異常
環境信息:
Spring Boot 3.4.4 , jdk 17 , springSecurity 6.4.4
問題背景:
沒有自定義controller ,改寫了login 頁面,并且進行了成功后的跳轉處理,發現無法進入到login.html 頁面,并且報錯。但是如果不進行改寫,login 則會自動被跳轉到默認登陸頁面,完成正常攔截。http://localhost:9999/bootscoder/login 比起我的改寫后的少了一個 后綴
問題復現:
核心代碼:
配置文件:
// 自定義表單登錄配置http.formLogin(form -> {form.loginPage("/login.html") // 自定義登錄頁面.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login")//登錄路徑,表單向該路徑提交,提交后自動執行UserDetailsService的方法.successForwardUrl("/main.html") // 使用重定向實現登錄成功后的跳轉
// .successHandler(new MyLoginSuccessHandler()) // 登錄成功處理器.failureForwardUrl("/fail.html");});// 設置放行的資源http.authorizeHttpRequests(authz -> {// 放行登錄頁、處理路徑、失敗頁和靜態資源authz.requestMatchers("/login.html", "/fail.html", "/main.html").permitAll();authz.requestMatchers("/css/*.css", "/js/*.js", "/img/**").permitAll();// 其余請求要求認證authz.anyRequest().authenticated();});// 關閉csrf防護(僅供測試使用,生產環境需謹慎關閉)
// http.csrf(csrf -> csrf.disable());
登陸頁面:
<div class="container"><h1>用戶登錄</h1><form action="/bootscoder/login" method="post"><input type="hidden" th:value = "${_csrf.token}" name="_csrf" th:if="${_csrf}"><div class="form-group"><label for="username">用戶名</label><input type="text" id="username" name="username" placeholder="請輸入用戶名" value="bootscoder" required></div><div class="form-group"><label for="password">密碼</label><input type="password" id="password" name="password" placeholder="請輸入密碼" value="root" required></div><input type="checkbox" name="remember-me" value="true"/>記住我</br><button type="submit">登錄</button></form>
</div>
yml前綴配置:
server:port: 9999servlet:context-path: /bootscoder # 所有接口前綴為 /bootscoder
錯誤信息:
This page isn’t working
localhost redirected you too many times.
Try deleting your cookies
ERR_TOO_MANY_REDIRECTS
分析原因
我的資源放到的是templates中, 也就是說 security 默認的登陸頁面在static中。
所以 當我 訪問templates下的路徑,由于沒有寫contoller 接口,會發生意外跳轉,被攔截后,跳轉到static 的默認登陸,然后由于我改寫了登陸page,所以又會跳回來,這樣導致了 重定向異常。
Spring Security 在配置登錄表單時,如果未能找到合適的頁面映射(比如你沒有正確配置自定義登錄頁面的訪問路徑),就可能會觸發默認的登錄頁面(默認在 static 目錄下)與自定義頁面之間的沖突,從而產生重定向循環。
解決方案
你的分析是正確的。如果你的登錄頁面放在了 templates 目錄下,而沒有額外通過 Controller 將其渲染,那么 Spring Boot 默認配置下實際上并不會訪問該模板,而是走了靜態資源(static 目錄)中的默認登錄頁面,從而產生了重定向循環。
下面有兩種解決方案:
方案一:將登錄頁面移動到 static 目錄
如果你不需要使用模板引擎渲染(例如 Thymeleaf 動態處理數據),可以直接把 login.html
文件放到 src/main/resources/static
目錄下,這樣資源可以直接被以靜態資源的形式訪問而不會依賴 Controller。
注意:這種方式下,你在 HTML 中寫死的路徑也需要注意避免重復拼接 context path。例如:
<form th:action="@{/login}" method="post">
或
<form action="/login" method="post">
(注意不要寫成
/bootscoder/login
,因為 Spring Boot 會自動處理 context path)
注意??:
不能使用MVC模板引擎:靜態資源文件夾中的HTML頁面不會被模板引擎(如Thymeleaf)處理,所以不能使用th:action
等Thymeleaf標簽。
頁面跳轉方式變化:
- 在模板引擎處理的頁面中,可以通過控制器方法返回視圖名稱來實現頁面跳轉
- 在靜態頁面中,需要使用JavaScript進行頁面重定向(
window.location.href
)或通過AJAX請求與后端交互
表單提交方式:
- 靜態頁面中的表單提交需要通過JavaScript攔截提交事件,使用
fetch
或XMLHttpRequest
等API發送請求到后端 - 不能依賴Spring MVC的表單處理機制直接提交到控制器方法
方案二:為模板頁面添加 Controller(推薦)
如果你需要利用模板引擎(如 Thymeleaf)進行頁面渲染,則需要提供一個 Controller 映射 GET 請求去返回該模板。例如,你可以這樣做:
package com.example.demo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class LoginController {@GetMapping("/login.html")public String login() {// 返回 templates/login.html 渲染結果return "login"; // 注意:返回值 "login" 對應 src/main/resources/templates/login.html}
}
安全配置修改
同時,確保你的 Spring Security 配置中放行登錄頁面、登錄處理 URL 以及失敗頁面,如下所示:
http.formLogin(form -> {form.loginPage("/login.html") // 自定義登錄頁面,由 Controller 渲染.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login") // 處理登錄請求,實際為 /bootscoder/login.defaultSuccessUrl("/main.html", true).failureUrl("/fail.html");
});http.authorizeHttpRequests(authz -> {// 放行登錄頁、登錄處理 URL、失敗頁和靜態資源authz.requestMatchers("/login.html", "/login", "/fail.html", "/main.html").permitAll();authz.requestMatchers("/css/*.css", "/js/*.js", "/img/**").permitAll();authz.anyRequest().authenticated();
});// 如果需要調試,可以先關閉 CSRF(僅用于測試環境)
// http.csrf(csrf -> csrf.disable());
同時,為靜態頁面(如 fail.html
和 main.html
)也建議確認它們能被正確訪問,如果你沒有為它們提供 Controller 渲染,那就把這類頁面放入 static
目錄中。
小結
- 放在
static
目錄:如果不依賴模板渲染,可以直接將login.html
放至static
,避免因找不到自定義 Controller 而觸發 Security 默認登錄頁。 - 使用 Controller 渲染模板:如果需要把頁面放在
templates
下利用 Thymeleaf 渲染,則必須提供映射該 GET 請求的 Controller,否則 Spring Security 可能依然會使用默認靜態的登錄頁面,造成重定向循環。