最新Spring?Security實(shí)戰(zhàn)教程之表單登錄定制到處理邏輯的深度改造(最新推薦)
前言
通過(guò)上一章節(jié)《最新Spring Security實(shí)戰(zhàn)教程(一)初識(shí)Spring Security安全框架》的講解介紹相信大家已經(jīng)認(rèn)識(shí) Spring Security
安全框架,在我們創(chuàng)建第一個(gè)項(xiàng)目演示中,相信大家發(fā)現(xiàn)了默認(rèn)表單登錄的局限性Spring Security
默認(rèn)提供的登錄頁(yè)雖然快速可用,但存在三大問(wèn)題:
- 界面風(fēng)格與業(yè)務(wù)系統(tǒng)不匹配
- 登錄成功/失敗處理邏輯固定
- 缺乏擴(kuò)展能力(如驗(yàn)證碼、多因子認(rèn)證)
本章節(jié)我們將Spring Security
默認(rèn)表單進(jìn)行登錄定制到處理邏輯的深度改造
改造準(zhǔn)備
現(xiàn)在在之前的Maven項(xiàng)目中創(chuàng)建第二個(gè)子模塊,命名 login-spring-secutity
,由于我們需要自定義登陸頁(yè),還需要追加引入 thymeleaf
模版框架
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
完整的maven項(xiàng)目結(jié)構(gòu)如下:
開(kāi)始登錄頁(yè)改造
我們第一步需要自定義自己的帶驗(yàn)證碼的登陸頁(yè),在 resources/templates 目錄下創(chuàng)建login.html
<!-- src/main/resources/templates/login.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>企業(yè)級(jí)登錄系統(tǒng)</title> <link rel="stylesheet" rel="external nofollow" rel="external nofollow" rel="external nofollow" > </head> <body> <div class="container d-flex justify-content-center align-items-center vh-100"> <div class="w-100" style="max-width: 400px;"> <div class="card"> <div class="card-body"> <h2 class="card-title text-center mb-4">登錄</h2> <form th:action="@{/login}" method="post"> <div class="mb-3"> <label for="username" class="form-label">用戶名</label> <input type="text" class="form-control" name="username" id="username" placeholder="請(qǐng)輸入用戶名"> </div> <div class="mb-3"> <label for="password" class="form-label">密碼</label> <input type="password" class="form-control" name="password" id="password" placeholder="請(qǐng)輸入密碼"> </div> <div class="d-grid gap-2"> <button type="submit" class="btn btn-primary">登錄</button> </div> <p class="mt-3 text-center"><a href="#" rel="external nofollow" rel="external nofollow" >忘記密碼?</a></p> </form> </div> </div> </div> </div> </body> </html>
添加一個(gè)默認(rèn)首頁(yè)index.html
,顯示登出按鈕
<!-- src/main/resources/templates/index.html --> <html xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>企業(yè)級(jí)登錄系統(tǒng)</title> <link rel="stylesheet" rel="external nofollow" rel="external nofollow" rel="external nofollow" > </head> <body> <h1>Hello Security</h1> <!-- 測(cè)試過(guò)程不需要關(guān)閉csrf防護(hù) --> <form th:action="@{/login}" method="post"> <button type="submit" class="btn btn-primary">Log Out</button> </form> <!-- 測(cè)試過(guò)程需要關(guān)閉csrf防護(hù) 否則404 --> <a th:href="@{/logout}" rel="external nofollow" >Log Out</a> </body> </html>
添加 contrller
配置首頁(yè)以及登陸頁(yè)
@Controller public class DemoTowController { @GetMapping("/login") public String login() { return "login"; } @GetMapping("/") public String index() { return "index"; } }
最后對(duì) Spring Security
進(jìn)行配置
@Configuration public class BasicSecurityConfig { // 配置安全策略 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http. authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") // 自定義登錄頁(yè)路徑 .permitAll() //不需要對(duì)login認(rèn)證 ) .logout(withDefaults()) .csrf(csrf -> csrf.disable()) //關(guān)閉csrf防護(hù) ; return http.build(); } }
測(cè)試訪問(wèn)默認(rèn)訪問(wèn)主頁(yè),由于主頁(yè)被攔截會(huì)自動(dòng)跳轉(zhuǎn)自login登陸頁(yè)
輸入正確用戶名密碼后,自動(dòng)返回主頁(yè),點(diǎn)擊登出按鈕自動(dòng)回到登錄頁(yè)
特別說(shuō)明:
注意登錄頁(yè)以及主頁(yè)登出,action
采用 @{}
生成URL,Spring Security會(huì)自動(dòng)幫我們生成name為_csrf 的隱藏表單,作用于 csrf 防護(hù)
如果你登出頁(yè)是 a 連接形式,為了保證登出不會(huì)404的問(wèn)題
1、我們先關(guān)閉 csrf 防護(hù) http.csrf(csrf -> csrf.disable())
2、登出按鈕使用表單方式 th:action="@{/logout}"
自定義用戶名密碼
到這里有小伙伴又要說(shuō)了,每次密碼都是Spring Security
自動(dòng)生成的UUID,能自定義用戶名密碼,答案是肯定的。Spring Security
提供了在Spring Boot配置文件設(shè)置用戶密碼功能
# 默認(rèn)安全配置(可通過(guò)application.yml覆蓋) spring: security: user: name: admin password: admin
登陸成功失敗跳轉(zhuǎn)問(wèn)題
通過(guò)上述代碼,小伙伴們看到登陸成功后,默認(rèn)返回系統(tǒng)主頁(yè) 即:index.html頁(yè)面,因?yàn)闃I(yè)務(wù)需求需要跳轉(zhuǎn)到別的頁(yè)面,如何配置?
Spring Security
配置類中 formLogin 提供了兩個(gè)參數(shù) defaultSuccessUrl 和 failureUrl 方便我們進(jìn)行配置
http.formLogin(form -> form .loginPage("/login") // 自定義登錄頁(yè)路徑 .defaultSuccessUrl("/home", true) // 登錄成功后跳轉(zhuǎn)路徑 .failureUrl("/login?error=true") // 登錄失敗后跳轉(zhuǎn)路徑 .permitAll() //不需要對(duì)login認(rèn)證 )
自定義登出
登出和登錄基本相同,由于篇幅問(wèn)題這里博主就不配置登出的頁(yè)面以及登出成功頁(yè)面了,主要看以下配置,相信大家都能理解了
http.logout(logout -> logout .logoutUrl("/logout") //自定義登出頁(yè) .logoutSuccessUrl("/login?logout") //登出成功跳轉(zhuǎn) )
前后端分離適配方案
上述的案例中針對(duì)的是前后端都在一個(gè)整體中的情況,針對(duì)現(xiàn)在前后端分離的項(xiàng)目我們?nèi)绾蝸?lái)進(jìn)行改造?我們處理以下問(wèn)題:
- 用戶登陸成功返回登陸成功 / 失敗 返回對(duì)應(yīng)JSON
- 用戶登出成功返回登出成功 / 失敗 返回對(duì)應(yīng)JSON
這里博主首先引入官方的一個(gè)介紹圖,如下:
我們發(fā)現(xiàn)在身份認(rèn)證管理器 AuthenticationManager
中, 有兩個(gè)結(jié)果 Success
以及 Failure
,最終交給 AuthenticationSuccessHandler
以及 AuthenticationFailureHandler
處理器處理。
簡(jiǎn)單總結(jié):
- 登錄成功調(diào)用:AuthenticationSuccessHandler
- 登錄失敗調(diào)用:AuthenticationFailureHandler
通過(guò)上面的講解,我們只需要自定義這兩個(gè)處理器即可,我們?cè)谂渲梦募性黾舆@兩個(gè)處理器,完整代碼如下:
// 自定義登錄成功處理器 @Configuration public class BasicSecurityConfig { // 配置安全策略 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http. authorizeHttpRequests(authorize -> authorize .requestMatchers("/ajaxLogin").permitAll() //ajax登陸頁(yè)不需要認(rèn)證 .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") // 自定義登錄頁(yè)路徑 // .defaultSuccessUrl("/", true) // 登錄成功后跳轉(zhuǎn)路徑 // .failureUrl("/login?error=true") // 登錄失敗后跳轉(zhuǎn)路徑 .successHandler(loginSuccessHandler()) .failureHandler(loginFailureHandler()) .permitAll() //不需要對(duì)login認(rèn)證 ) .logout(withDefaults()) .csrf(csrf -> csrf.disable()) //關(guān)閉csrf防護(hù) ; return http.build(); } // 自定義登錄成功處理器 @Bean public AuthenticationSuccessHandler loginSuccessHandler() { return (request, response, authentication) -> { if (isAjaxRequest(request)) { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write("{\"code\":200, \"message\":\"/認(rèn)證成功\"}"); } else { response.sendRedirect("/"); } }; } // 自定義登錄失敗處理器 @Bean public AuthenticationFailureHandler loginFailureHandler() { return (request, response, exception) -> { if (isAjaxRequest(request)) { response.setCharacterEncoding("UTF-8"); response.getWriter().write("{\"code\":401, \"message\":\"認(rèn)證失敗\"}"); } else { response.sendRedirect("/login?error=true"); } }; } //判斷是否ajax請(qǐng)求 public boolean isAjaxRequest(HttpServletRequest request) { String xRequestedWith = request.getHeader("X-Requested-With"); return "XMLHttpRequest".equals(xRequestedWith); }
最后新增一個(gè)ajaxLogin.html 使用ajax發(fā)送請(qǐng)求(為了測(cè)試方便這里就簡(jiǎn)單創(chuàng)建一個(gè),不使用VUE等工程了)
<!-- src/main/resources/templates/ajaxLogin.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>企業(yè)級(jí)登錄系統(tǒng)</title> <link rel="stylesheet" rel="external nofollow" rel="external nofollow" rel="external nofollow" > <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> </head> <body> <div class="container d-flex justify-content-center align-items-center vh-100"> <div class="w-100" style="max-width: 400px;"> <div class="card"> <div class="card-body"> <h2 class="card-title text-center mb-4">登錄</h2> <form> <div class="mb-3"> <label for="username" class="form-label">用戶名</label> <input type="text" class="form-control" name="username" id="username" placeholder="請(qǐng)輸入用戶名"> </div> <div class="mb-3"> <label for="password" class="form-label">密碼</label> <input type="password" class="form-control" name="password" id="password" placeholder="請(qǐng)輸入密碼"> </div> <div class="d-grid gap-2"> <button type="submit" class="btn btn-primary">登錄</button> </div> <p class="mt-3 text-center"><a href="#" rel="external nofollow" rel="external nofollow" >忘記密碼?</a></p> </form> </div> </div> </div> </div> <script> $(document).ready(function () { $('form').submit(function (event) { event.preventDefault(); var username = $('#username').val(); var password = $('#password').val(); $.ajax({ type: 'POST', url: '/login', data: { username: username, password: password }, success: function (response) { console.log(response) if(response.code ==200){ window.location.href = '/'; } } }) }) }) </script> </body> </html>
controller 中追加頁(yè)面展示
@GetMapping("/ajaxLogin") public String ajaxLogin() { return "ajaxLogin"; }
最后啟動(dòng)Spring Boot項(xiàng)目,訪問(wèn) /ajaxLogin 登陸頁(yè),測(cè)試輸入正確和不正確的賬號(hào)密碼進(jìn)行測(cè)試,并觀察瀏覽器控制臺(tái)輸出
結(jié)語(yǔ)
本章節(jié)介紹了如何通過(guò)Spring Security
實(shí)現(xiàn)從配置自定義登錄頁(yè)面、表單登錄處理邏輯的配置,并簡(jiǎn)單模擬了前后分離的適配方案。小伙伴們可以跟著博主的樣例代碼自己敲一遍進(jìn)行相關(guān)測(cè)試!如果本本章內(nèi)容對(duì)您有所幫助,希望 一鍵三連 給博主一點(diǎn)點(diǎn)鼓勵(lì),如果您有任何疑問(wèn)或建議,請(qǐng)隨時(shí)留言討論!
在接下來(lái)的章節(jié)中,我們將逐步深入 Spring Security
的各個(gè)技術(shù)細(xì)節(jié),帶你從入門到精通,全面掌握這一安全技術(shù)的方方面面。歡迎繼續(xù)關(guān)注!
到此這篇關(guān)于最新Spring Security實(shí)戰(zhàn)教程之表單登錄定制到處理邏輯的深度改造(最新推薦)的文章就介紹到這了,更多相關(guān)Spring Security表單登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析
- Spring?Security登錄表單配置示例詳解
- SpringSecurity?表單登錄的實(shí)現(xiàn)
- SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例
- SpringSecurity 自定義表單登錄的實(shí)現(xiàn)
- SpringSecurity 默認(rèn)表單登錄頁(yè)展示流程源碼
- Spring Security 表單登錄功能的實(shí)現(xiàn)方法
- Spring Security在標(biāo)準(zhǔn)登錄表單中添加一個(gè)額外的字段
相關(guān)文章
JavaCV實(shí)現(xiàn)讀取視頻信息及自動(dòng)截取封面圖詳解
javacv可以幫助我們?cè)趈ava中很方便的使用OpenCV以及FFmpeg相關(guān)的功能接口。本文將利用Javacv實(shí)現(xiàn)在視頻網(wǎng)站中常見(jiàn)的讀取視頻信息和自動(dòng)獲取封面圖的功能,感興趣的可以了解一下2022-06-06自定義一個(gè)異常類模板的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇自定義一個(gè)異常類模板的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10java?WebSocket?服務(wù)端實(shí)現(xiàn)代碼
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端,這篇文章主要介紹了java?WebSocket?服務(wù)端代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02Java設(shè)計(jì)模式七大原則之接口隔離原則詳解
接口隔離原則(Interface Segregation Principle),又稱為ISP原則,就是在一個(gè)類中不要定義過(guò)多的方法,接口應(yīng)該盡量簡(jiǎn)單細(xì)化。本文將為大家具體介紹一下Java設(shè)計(jì)模式七大原則之一的接口隔離原則,需要的可以參考一下2022-02-02Apache?Maven3.6.0的下載安裝和環(huán)境配置(圖文教程)
本文主要介紹了Apache?Maven3.6.0的下載安裝和環(huán)境配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Maven重復(fù)依賴問(wèn)題解決(同一個(gè)jar多個(gè)版本)
本文主要介紹了Maven重復(fù)依賴問(wèn)題解決(同一個(gè)jar多個(gè)版本),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06idea快速搭建spring cloud注冊(cè)中心與注冊(cè)的方法
這篇文章主要介紹了idea快速搭建spring cloud注冊(cè)中心與注冊(cè)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Springboot任務(wù)之異步任務(wù)的使用詳解
今天學(xué)習(xí)了一個(gè)新技能SpringBoot實(shí)現(xiàn)異步任務(wù),所以特地整理了本篇文章,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Java實(shí)現(xiàn)List反轉(zhuǎn)的方法總結(jié)
在Java中,反轉(zhuǎn)一個(gè)List意味著將其元素的順序顛倒,使得第一個(gè)元素變成最后一個(gè),最后一個(gè)元素變成第一個(gè),依此類推,這一操作在處理數(shù)據(jù)集合時(shí)非常有用,所以本文給大家總結(jié)了Java實(shí)現(xiàn)List反轉(zhuǎn)的方法,需要的朋友可以參考下2024-04-04