SpringSecurity——前后端分離登錄認證的整個過程
前端:
使用Axios向后端發送請求
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登錄</title><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<form>用戶名:<input type="text" name="username" id="username"><br>密碼:<input type="password" name="password" id="password"><br><input type="button" value="登錄" onclick="login()">
</form>
</body>
<script>function login() {var username = document.getElementById("username").value;var password = document.getElementById("password").value;let formData = new FormData(); // 創建formData對象formData.append("username", username);formData.append("password", password);axios.post("http://localhost:8080/login", formData).then(function (response) {console.log(response);if (response.data.code === 200) {alert("登錄成功");window.location.href = "welcome.html";} else {alert("登錄失敗");}}).catch(function (error){console.log(error);});}
</script></html>
?后端:
Spring Security 配置指定該 URL 作為登錄處理入口。
@Configuration
@EnableMethodSecurity
// 配置spring的容器
public class SecurityConfig {@Bean// 安全過濾器鏈Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity ,CorsConfigurationSource configurationSource ) throws Exception { //httpSecurity方法參數注入Beanreturn httpSecurity// 配置自己的登錄頁面.formLogin( (formLogin) ->{formLogin.loginProcessingUrl("/login") // 登錄賬戶密碼往哪個地址提交}) .build();}
}
由于現在是前后端分離的,所以拿不到CSRF,因此我們需要先禁用CSRF:
禁用 CSRF:
在基于 Token 的認證中,由于不依賴 Session,因此通常會關閉 CSRF 保護。可以在 HttpSecurity 中調用 .csrf().disable()
來實現這一點。
@Configuration
@EnableMethodSecurity
// 配置spring的容器
public class SecurityConfig {@Bean// 安全過濾器鏈Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity ,CorsConfigurationSource configurationSource ) throws Exception { //httpSecurity方法參數注入Beanreturn httpSecurity// 配置自己的登錄頁面.formLogin( (formLogin) ->{formLogin.loginProcessingUrl("/login") // 登錄賬戶密碼往哪個地址提交}) .csrf((csrf)->{// 禁止csrf跨站請求,禁用之后,肯定就不安全了,有csrf網絡攻擊的風險,后續加入jwt是可以防御的csrf.disable();}).build();}
}
配置 formLogin.loginProcessingUrl("/login")
實際上告訴 Spring Security:
- 當收到指向
/login
的 POST 請求時,Spring Security 內部的過濾器(例如UsernamePasswordAuthenticationFilter
)會攔截這個請求,并自動處理用戶的認證邏輯。 - 你不需要在自己的 Controller 中實現這個
/login
接口,因為 Spring Security 會接管并執行用戶名、密碼的驗證,以及后續的成功或失敗處理(如果你配置了相應的successHandler
和failureHandler
)。
存在跨域問題:
協議不同會跨域 ?https://localhost:8080????http://localhost:8080
- 端口不同會跨域:http://localhost:10492????http://localhost:8080?
- 域名不同會跨域:http://bjpowernode.com???http://baidu.com?
三個里面有任何一個不同,都是跨域,跨域是瀏覽器不允許的,瀏覽器是為了安全,不允許你跨域訪問
跨域資源共享(CORS)配置
- 允許跨域訪問:
前后端分離架構中,前端通常與后端不在同一個域名下,因此必須在后端配置 CORS 策略。可以通過在 HttpSecurity 配置中調用.cors()
方法通常需要自己定義一個 CorsConfigurationSource Bean。在自己定義?CorsConfigurationSource Bean當中我們需要返回CorsConfigurationSource(接口)的實現類——通常情況下是UrlBasedCorsConfigurationSource
@Configuration
@EnableMethodSecurity
// 配置spring的容器
public class SecurityConfig {/*** 配置跨域* @return*/@Beanpublic CorsConfigurationSource configurationSource() {UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();// 跨域配置CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowedOrigins(Arrays.asList("*")); // 允許的請求來源corsConfiguration.setAllowedMethods(Arrays.asList("*")); // 允許的請求方法corsConfiguration.setAllowedHeaders(Arrays.asList("*"));// 允許的請求頭// 注冊配置urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);return urlBasedCorsConfigurationSource;}@Bean// 安全過濾器鏈Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity ,CorsConfigurationSource configurationSource ) throws Exception { //httpSecurity方法參數注入Beanreturn httpSecurity// 配置自己的登錄頁面.formLogin( (formLogin) ->{formLogin.loginProcessingUrl("/login") // 登錄賬戶密碼往哪個地址提交}) .csrf((csrf)->{// 禁止csrf跨站請求,禁用之后,肯定就不安全了,有csrf網絡攻擊的風險,后續加入jwt是可以防御的csrf.disable();}).cors((cors)->{ // 允許前端跨域訪問cors.configurationSource( configurationSource);}).build();}
}
憑證接收和驗證:?Spring Security框架使用 UsernamePasswordAuthenticationFilter
攔截請求,獲取用戶提交的賬號和密碼。
用戶信息查詢:
UserServiceImpl重寫loadUserByUsername方法
從數據庫中加載用戶信息,返回一個包含用戶狀態和權限信息的 UserDetails
(在本例中為 TUser 對象)。
重寫loadUserByUsername方法需要
service接口,需要繼承springsecurity框架的UserDetailsService接口
// 我們的處理登錄的service接口,需要繼承springsecurity框架的UserDetailsService接口
public interface UserService extends UserDetailsService {
}
service實現類?
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 通過用戶名查詢數據庫TUser user = userMapper.selectByLoginAct(username);if (user == null){throw new UsernameNotFoundException("用戶不存在");}return user; // 實現了UserDetails接口,包含所有字段}
狀態檢查和密碼比對: 框架會檢查用戶對象的狀態(例如賬戶是否有效)以及比對密碼是否匹配。
登錄成功或失敗的處理(處理器):
根據認證結果決定登錄成功后的跳轉(默認跳轉到上一次請求的地址或項目根路徑)或失敗后的處理(重定向到 /login?error
),但在前后端分離場景中,需要通過自定義 Handler 返回 JSON 格式的響應。
@Configuration
@EnableMethodSecurity
// 配置spring的容器
public class SecurityConfig {@Bean// 安全過濾器鏈Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity ,CorsConfigurationSource configurationSource ) throws Exception { //httpSecurity方法參數注入Beanreturn httpSecurity// 配置自己的登錄頁面.formLogin( (formLogin) ->{formLogin.loginProcessingUrl("/login") // 登錄賬戶密碼往哪個地址提交.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler); // 登錄失敗的回調}) .build();}
}
?MyAuthenticationSuccessHandle(登錄成功的處理器):
@Component
public class MyAuthenticationSuccessHandle implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {R result = R.builder().code(200).msg("登錄成功").info(authentication.getPrincipal()).build();response.setContentType("application/json;charset=utf-8");String json = JSONUtil.toJsonStr(result);response.getWriter().write(json);}
}
?MyAuthenticationFailHandle(登錄失敗的處理器):
@Component
public class MyAuthenticationFailHandle implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {R result = R.builder().code(500).msg("登錄失敗").info(exception.getMessage()).build();response.setContentType("application/json;charset=utf-8");String json = JSONUtil.toJsonStr(result);response.getWriter().write(json);}
}
退出成功的處理器
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {R result = R.builder().code(200).msg("退出成功").info(authentication.getPrincipal()).build();response.setContentType("application/json;charset=utf-8");String json = JSONUtil.toJsonStr(result);response.getWriter().write(json);}
}
在 Spring Security 中,退出操作(logout)的設計邏輯與登錄有所不同。登錄過程涉及用戶憑證驗證、狀態檢查和密碼匹配等環節,這些都有可能失敗,所以需要提供失敗處理器(如登錄失敗處理器)。而退出操作本質上只是清理會話、清空 SecurityContext 等動作,通常不會出現“失敗”的情況。因此,框架只提供了退出成功處理器(LogoutSuccessHandler),而沒有專門的退出失敗處理器。
無狀態認證的考慮:
由于不再使用傳統 Session 記錄用戶狀態,后續訪問其他需要認證的接口時會提示未登錄,此時通常會引入 JWT 等機制來維持用戶認證狀態。
沒有session、前端cookie中也不會存儲sessionid;那么這樣的話,用戶狀態怎么保持呢?
需要使用我們下面介紹的jwt解決該問題;
不分離的:Tomcat 【thymeleaf ?<----> (controller、sucurity)】
前后端分離:Nginx 【Vue】 ?<----jwt----> ?Tomcat 【 (controller、sucurity) 】