Spring Security實現(xiàn)5次密碼錯誤觸發(fā)賬號自動鎖定功能
1. 前言
在現(xiàn)代互聯(lián)網(wǎng)應(yīng)用中,賬號安全是重中之重。然而,暴力 破解攻擊依然是最常見的安全威脅之一。攻擊者通過自動化腳本嘗試大量的用戶名和密碼組合,試圖找到漏洞進入系統(tǒng)。盡管我們可以通過復(fù)雜密碼要求、驗證碼、雙因子認證等方式來提高安全性,但這些方法無法完全杜絕暴力 破解的風險。
為了解決這一問題,賬號鎖定機制被廣泛應(yīng)用。通過限制用戶的密碼輸入錯誤次數(shù),當達到一定次數(shù)時鎖定賬號,可以有效防范暴力 破解攻擊,提高系統(tǒng)安全性。
以下是引入賬號鎖定機制的幾大原因:
- 防止暴力 破解攻擊:攻擊者短時間內(nèi)多次嘗試密碼時,鎖定機制能阻斷其進一步操作。
- 保護用戶資產(chǎn)和隱私:避免賬號被盜造成的財產(chǎn)損失和隱私泄露。
- 提升系統(tǒng)安全性:通過限制無效登錄嘗試次數(shù),降低因密碼弱點引發(fā)的安全隱患。
- 應(yīng)對安全合規(guī)要求:許多行業(yè)標準(如 GDPR、ISO27001)建議對異常登錄行為采取保護措施。
在本文中,我們將通過 Spring Security 來實現(xiàn)一個簡單而高效的賬號鎖定機制。當用戶密碼連續(xù)輸入錯誤達到 5 次后,系統(tǒng)將自動鎖定該賬號3分鐘。接下來,我們將通過技術(shù)流程、表結(jié)構(gòu)設(shè)計、核心代碼實現(xiàn)以及效果測試,詳細講解如何在 Spring Security 中落地這一功能。
2. 技術(shù)流程詳解
2.1. 技術(shù)流程講解
基于SpringSecurity實現(xiàn)密碼錯誤鎖定賬號的流程如下:
其實光看流程圖已經(jīng)能很清晰地理解完整流程,我這邊還是用文字再給大家梳理一遍:
1. 用戶提交登錄請求
用戶輸入用戶名和密碼后發(fā)起登錄請求,系統(tǒng)開始驗證其身份。
2. 加載用戶信息
Spring Security 使用自定義的 UserService
加載用戶信息,包括用戶名、密碼、鎖定狀態(tài)(accountNonLocked
)、登錄失敗次數(shù)(loginAttempts
)、鎖定時間(lockTime
)等。
3. 檢查賬號鎖定狀態(tài)
- 如果賬號被鎖定:
- 判斷鎖定時間是否已過:
- 鎖定時間未過:拋出
LockedException
,阻止登錄。 - 鎖定時間已過:解鎖賬號(
accountNonLocked = true
)、重置登錄失敗次數(shù)(loginAttempts = 0
),并允許繼續(xù)登錄。
- 鎖定時間未過:拋出
- 判斷鎖定時間是否已過:
- 如果賬號未鎖定,直接進入密碼驗證。
4. 驗證密碼
- 密碼正確:
- 登錄成功,重置用戶狀態(tài)(失敗次數(shù)清零,鎖定狀態(tài)解除),并更新到數(shù)據(jù)庫。
- 密碼錯誤:
- 增加登錄失敗次數(shù)(
loginAttempts + 1
)。 - 如果失敗次數(shù)達到 5 次或以上,鎖定賬號(
accountNonLocked = false
),并記錄當前鎖定時間(lockTime = 當前時間
)。
- 增加登錄失敗次數(shù)(
5. 更新用戶狀態(tài)
- 登錄成功時:更新用戶信息到數(shù)據(jù)庫,并執(zhí)行登錄成功后的業(yè)務(wù)邏輯。
- 登錄失敗時:更新用戶的失敗次數(shù)和鎖定狀態(tài)到數(shù)據(jù)庫,并執(zhí)行失敗后的處理邏輯。
6. 等待下次登錄嘗試
用戶根據(jù)賬號狀態(tài)和鎖定時間,決定何時再次發(fā)起登錄請求。
2.2. 涉及到的SpringSecurity核心組件
以上流程涉及到的SpringSecurity核心組件如下:
1. AuthenticationManager
- 管理認證流程,調(diào)用
AuthenticationProvider
驗證用戶憑據(jù)。
2. AuthenticationProvider
- 執(zhí)行具體的認證邏輯,包括密碼驗證和賬號狀態(tài)檢查。
3. PasswordEncoder
- 用于密碼加密和驗證,例如使用
BCryptPasswordEncoder
。
4. AuthenticationSuccessHandler
- 處理登錄成功后的邏輯,例如重置失敗登錄次數(shù)和解鎖賬號。
5. AuthenticationFailureHandler
- 處理登錄失敗后的邏輯,例如增加登錄失敗次數(shù)和鎖定賬號。
6. AuthenticationEntryPoint
- 處理未認證用戶訪問受保護資源時的邏輯,返回錯誤信息。
7. SecurityContextHolder
- 存儲和提供當前用戶的認證信息(
Authentication
對象)。
8. ExceptionTranslationFilter
- 捕獲認證過程中拋出的異常,并交給
AuthenticationEntryPoint
或AuthenticationFailureHandler
處理。
9. UsernamePasswordAuthenticationFilter
- 處理基于用戶名和密碼的登錄請求,觸發(fā)認證流程。
10.HttpSecurity
- 配置認證流程和組件,包括認證、授權(quán)和異常處理。
這些組件共同協(xié)作,實現(xiàn) Spring Security 的認證和密碼錯誤鎖定賬號功能。
3. 技術(shù)實現(xiàn)
3.1. 表結(jié)構(gòu)設(shè)計
密碼錯誤賬號鎖定只涉及到user表,這是我的表結(jié)構(gòu),你可以根據(jù)你自己的靈活調(diào)整:
CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, login_attempts INT DEFAULT 0, -- 登錄失敗次數(shù) lock_time TIMESTAMP NULL, -- 賬號鎖定時間 account_non_locked BOOLEAN DEFAULT TRUE -- 賬號是否鎖定 );
3.2. 核心代碼
1. 編寫自定義UsernamePasswordAuthenticationFilter
public class SecurUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Autowired ISysUserService userService; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) { ObjectMapper mapper = new ObjectMapper(); UsernamePasswordAuthenticationToken authRequest = null; //取authenticationBean Map<String, String> authenticationBean = null; //用try with resource,方便自動釋放資源 try (InputStream is = request.getInputStream()) { authenticationBean = mapper.readValue(is, Map.class); } catch (IOException e) { //將異常放到自定義的異常類中 throw new SecurAuthenticationException(e.getMessage()); } try { if (!authenticationBean.isEmpty()) { //獲得賬號、密碼 String username = authenticationBean.get(SPRING_SECURITY_FORM_USERNAME_KEY); String password = authenticationBean.get(SPRING_SECURITY_FORM_PASSWORD_KEY); request.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username); //檢測賬號、密碼是否存在 if (userService.checkLogin(username, password)) { //將賬號、密碼裝入UsernamePasswordAuthenticationToken中 authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request,authRequest ); return this.getAuthenticationManager().authenticate(authRequest); } } } catch (Exception e) { throw new SecurAuthenticationException(e.getMessage()); } return null; } else { return this.attemptAuthentication(request, response); } } }
這段代碼是一個自定義的 Spring Security 登錄過濾器,用于處理 JSON 格式的登錄請求(代替默認的表單登錄)。它通過解析請求體中的 JSON,提取用戶名和密碼,并調(diào)用用戶服務(wù)檢查登錄信息是否正確。如果驗證通過,則創(chuàng)建并返回一個 UsernamePasswordAuthenticationToken
,交由認證管理器執(zhí)行進一步的認證流程;否則,拋出自定義異常終止認證。
2. 編寫userService.checkLogin方法
public boolean checkLogin(String username, String rawPassword) throws Exception { SysUser userEntity = this.getUserByUserName(username); System.out.println("userEntity = " + userEntity); if (userEntity == null) { //System.out.println("checkLogin--------->賬號不存在,請重新嘗試!"); //設(shè)置友好提示 throw new Exception("賬號不存在,請重新嘗試!"); } else { // 檢查賬號鎖定狀態(tài) handleAccountLock(userEntity); //加密的密碼 String encodedPassword = userEntity.getPassword(); //和加密后的密碼進行比配 if (!passwordEncoder.matches(rawPassword, encodedPassword)) { //System.out.println("checkLogin--------->密碼不正確!"); //設(shè)置友好提示 throw new Exception("密碼不正確!"); } else { return true; } } }
這段代碼實現(xiàn)了用戶登錄驗證邏輯,首先通過用戶名獲取用戶信息,如果用戶不存在則拋出異常提示賬號不存在。接著檢查用戶賬號的鎖定狀態(tài)(調(diào)用 handleAccountLock 方法),如果賬號未鎖定,則驗證用戶輸入的原始密碼是否與加密存儲的密碼匹配。密碼匹配成功則返回 true,否則拋出異常提示密碼不正確。
3. 編寫 handleAccountLock方法
/** * 檢查并處理賬號鎖定邏輯 */ private void handleAccountLock(SysUser user) { if (!user.getAccountNonLocked() && user.getLockTime() != null) { // 當前時間 Date now = new Date(); // 解鎖時間 Date unlockTime = new Date(user.getLockTime().getTime() + LOCK_DURATION.toMillis()); if (now.before(unlockTime)) { throw new LockedException("賬號已鎖定,請3分鐘后再試"); } // 解鎖賬號并重置狀態(tài) user.setAccountNonLocked(true); user.setLoginAttempts(0); user.setLockTime(null); this.updateById(user); } }
這段代碼實現(xiàn)了賬號鎖定狀態(tài)的檢查與處理邏輯。如果用戶賬號已鎖定且存在鎖定時間,系統(tǒng)會計算解鎖時間。如果當前時間在解鎖時間之前,則拋出異常提示賬號被鎖定;如果超過了解鎖時間,則解鎖賬號,同時重置失敗次數(shù)和鎖定時間,并更新用戶信息到數(shù)據(jù)庫。
4. 編寫increaseFailedAttempts,用于用戶登錄失敗時調(diào)用
public void increaseFailedAttempts(SysUser user) { int attempts = user.getLoginAttempts() + 1; if (attempts >= MAX_LOGIN_ATTEMPTS) { user.setAccountNonLocked(false); user.setLockTime(new Date()); } user.setLoginAttempts(attempts); this.updateById(user); }
這段代碼實現(xiàn)了增加用戶登錄失敗次數(shù)的邏輯。當用戶登錄失敗時,登錄失敗次數(shù) (loginAttempts
) 增加 1;如果失敗次數(shù)達到或超過最大允許次數(shù) (MAX_LOGIN_ATTEMPTS
),系統(tǒng)會將用戶賬號設(shè)置為鎖定狀態(tài) (accountNonLocked=false
) 并記錄鎖定時間 (lockTime
)。最后,將更新后的用戶狀態(tài)保存到數(shù)據(jù)庫。
5. 編寫resetLoginAttempts,用于用戶登錄成功時調(diào)用
public void resetLoginAttempts(String username) { SysUser user = this.getUserByUserName(username); if(user==null){ throw new UsernameNotFoundException(String.format("%s.這個用戶不存在或已被禁用", username)); } user.setLoginAttempts(0); user.setAccountNonLocked(true); user.setLockTime(null); this.updateById(user); }
這段代碼實現(xiàn)了重置用戶登錄失敗次數(shù)的邏輯。通過用戶名查詢用戶信息,如果用戶不存在則拋出異常。若用戶存在,則將其登錄失敗次數(shù) (loginAttempts
) 重置為 0,解鎖賬號 (accountNonLocked=true
),并清除鎖定時間 (lockTime=null
)。最后,將更新后的用戶信息保存到數(shù)據(jù)庫。
6. 編寫自定義AuthenticationSuccessHandler,在用戶登錄成功時調(diào)用resetLoginAttempts
7. 編寫自定義AuthenticationFailureHandler
@Component public class SecurAuthenticationFailureHandler extends JSONAuthentication implements AuthenticationFailureHandler { @Autowired SysUserServiceImpl sysUserService; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { // 從請求體中解析 JSON,獲取 username String username = (String) request.getAttribute(SPRING_SECURITY_FORM_USERNAME_KEY); // 登錄失敗時增加失敗次數(shù) if (username != null) { SysUser user = sysUserService.getUserByUserName(username); if (user != null) { sysUserService.increaseFailedAttempts(user); } } ResponseStructure data = ResponseStructure.instance(ALL_RETURN_401.getCode(),"登錄失敗:"+e.getMessage()); //輸出 this.WriteJSON(request, response, data); } }
這段代碼是一個自定義的登錄失敗處理器 SecurAuthenticationFailureHandler,在用戶登錄失敗時被觸發(fā)。它通過解析請求獲取登錄失敗的用戶名,并調(diào)用服務(wù)方法 increaseFailedAttempts 增加用戶的登錄失敗次數(shù)。同時,根據(jù)異常信息生成統(tǒng)一的響應(yīng)結(jié)構(gòu),并以 JSON 格式返回錯誤信息給客戶端,提示登錄失敗的原因。
4. 結(jié)語
本文詳細講解了如何通過 Spring Security 實現(xiàn)密碼錯誤多次后自動鎖定賬號的功能。從需求分析到技術(shù)流程,再到核心代碼實現(xiàn),我們完整梳理了賬號鎖定機制的設(shè)計與落地方法。
賬號鎖定機制是提升系統(tǒng)安全性的重要手段,能夠有效防范暴力 破解攻擊。在實際應(yīng)用中,還可以結(jié)合具體需求進行優(yōu)化,例如添加解鎖通知或動態(tài)調(diào)整鎖定策略等。
以上就是Spring Security實現(xiàn)5次密碼錯誤觸發(fā)賬號自動鎖定功能的詳細內(nèi)容,更多關(guān)于Spring Security密碼錯誤賬號鎖定的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA
這篇文章主要介紹了關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA的問題,文中提供了詳細的圖文講解,需要的朋友可以參考下,如果有錯誤的地方還請指正2023-03-03JAVAEE model1模型實現(xiàn)商品瀏覽記錄(去除重復(fù)的瀏覽記錄)(一)
這篇文章主要為大家詳細介紹了JAVAEE model1模型實現(xiàn)商品瀏覽記錄,去除重復(fù)的瀏覽記錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11springboot項目或其他項目使用@Test測試項目接口配置
這篇文章主要介紹了springboot項目或其他項目使用@Test測試項目接口配置,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07Java連接SAP RFC實現(xiàn)數(shù)據(jù)抽取的示例詳解
這篇文章主要為大家學習介紹了Java如何連接SAP RFC實現(xiàn)數(shù)據(jù)抽取的功能,文中的示例代碼講解詳細,具有一定的參考價值,需要的可以了解下2023-08-08SpringBoot使用Redis對用戶IP進行接口限流的項目實踐
本文主要介紹了SpringBoot使用Redis對用戶IP進行接口限流,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07