了解 Spring Security 和 OAuth
在本文中,我們將探索 Spring 安全性,並使用 OAuth 2.0 建構一個驗證系統。
Spring Security 是一個功能強大、高度可自訂的框架,用於在基於 Java 的應用程式中實現強大的身份驗證和存取控制機制。它是 Spring 生態系統的核心元件,廣泛用於保護 Web 應用程式、REST API 和其他後端服務的安全。透過 Spring Security,您可以為在應用程式中建立和實施安全實踐奠定堅實的基礎。
Spring Security 的工作原理
在深入了解 Spring Security 的運作方式之前,了解基於 Java 的 Web 伺服器中的請求處理生命週期至關重要。 Spring Security 無縫整合到此生命週期中以保護傳入請求。
使用 Spring Security 處理請求生命週期
使用 Spring Security 在基於 Spring 的應用程式中處理 HTTP 請求的生命週期涉及多個階段,每個階段在處理、驗證和保護請求方面都發揮關鍵作用。
1. 客戶請求
當客戶端(例如瀏覽器、行動應用程式或 Postman 等 API 工具)向伺服器發送 HTTP 請求時,生命週期就開始了。
範例:
GET /api/admin/dashboard HTTP/1.1
2. Servlet 容器
Servlet 容器(例如 Tomcat)接收請求並將其委託給 DispatcherServlet(Spring 應用程式中的前端控制器)。這是應用程式處理管道開始的地方。
3. Spring Security 過濾器鏈
在 DispatcherServlet 處理請求之前,Spring Security 的過濾器鏈 會攔截它。過濾器鍊是一系列過濾器,每個過濾器負責處理特定的安全任務。這些過濾器可確保請求在到達應用程式邏輯之前滿足身份驗證和授權要求。
鏈中的關鍵過濾器:
驗證過濾器:
這些過濾器驗證請求是否包含有效的憑證,例如使用者名稱/密碼、JWT 或會話 cookie。授權過濾器:
身份驗證後,這些過濾器可確保經過身份驗證的使用者俱有存取所請求資源所需的角色或權限。其他濾鏡:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
4. 安全上下文
如果驗證成功,Spring Security 將建立一個 Authentication 物件並將其儲存在 SecurityContext 中。該物件通常儲存在執行緒本地儲存中,在整個請求生命週期中都可以存取。
驗證物件:
Principal:代表經過驗證的使用者(例如使用者名稱)。
憑證:包含 JWT 令牌或密碼等驗證詳細資訊。
權限:包含指派給使用者的角色和權限。
過濾器鏈中的範例流程:
請求通過驗證過濾器。
如果憑證有效,則會建立 Authentication 物件並將其新增至 SecurityContext。
如果憑證無效,ExceptionTranslationFilter 會傳送 401 未經授權的回應給客戶端。
5. DispatcherServlet
一旦要求成功通過 Spring Security 過濾器鏈,DispatcherServlet 就會接管:
處理程序映射:
它根據 URL 和 HTTP 方法將傳入請求對應到適當的控制器方法。控制器呼叫:
映射的控制器通常在服務和儲存庫等其他 Spring 元件的幫助下處理請求並傳回適當的回應。
Spring Security 如何融入生命週期
Spring Security 透過其過濾器將自身整合到此生命週期中,在最早階段攔截請求。當請求到達應用程式邏輯時,它已經經過身份驗證和授權,確保核心應用程式僅處理合法流量。
Spring Security 的設計確保以聲明方式處理身份驗證、授權和其他安全措施,使開發人員能夠根據需要靈活地自訂或擴展其行為。它不僅強制執行最佳實踐,還簡化了現代應用程式中複雜安全要求的實作。
Spring Security 元件:超越過濾器鏈
探索了 Spring Security 中的過濾器鏈之後,讓我們深入研究一些在身份驗證和授權過程中發揮關鍵作用的其他關鍵組件。
認證管理器
AuthenticationManager 是一個定義單一方法的接口,authenticate(Authenticationauthentication),用於驗證使用者的憑證並確定它們是否有效。您可以將 AuthenticationManager 視為一個協調器,您可以在其中註冊多個提供者,並且根據請求類型,它將向正確的提供者發送身份驗證請求。
認證提供者
AuthenticationProvider 是一個接口,它定義了一個合約,用於根據使用者的憑證對使用者進行身份驗證。它代表特定的身份驗證機制,例如使用者名稱/密碼、OAuth 或 LDAP。多個 AuthenticationProvider 實作可以共存,允許應用程式支援各種身份驗證策略。
核心概念:
驗證物件:
AuthenticationProvider 處理 Authentication 對象,該對象封裝了使用者的憑證(例如使用者名稱和密碼)。驗證方法:
每個 AuthenticationProvider 都實作了authenticate(Authenticationauthentication)方法,實際的身份驗證邏輯駐留在其中。此方法:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
- 支持方法: support(Class>authentication) 方法指示 AuthenticationProvider 是否可以處理給定類型的驗證。這允許 Spring Security 確定正確的提供者來處理特定的身份驗證請求。
範例:
資料庫支援的 AuthenticationProvider 驗證使用者名稱和密碼。
基於 OAuth 的 AuthenticationProvider 驗證外部身分提供者所頒發的令牌。
用戶詳情服務
UserDetailsService 在 Spring 文件中被描述為加載用戶特定資料的核心接口,它包含一個方法 loadUserByUsername ,該方法接受用戶名作為參數並返回 ==User== 身份對象。基本上我們建立並實作了 UserDetailsService 的類,在其中重寫了 loadUserByUsername 方法。
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
現在這三者如何協同工作的是 AuthenticationManager 會要求 AuthenticationProvider 根據指定的 Provider 類型進行身份驗證,而 UserDetailsService 實作將幫助 AuthenticationProvider 證明使用者詳細資料。
現在,在進行設定和所有內容之前,這裡是基於 JWT 身份驗證的 Spring Security 的簡明流程:
1. 使用者請求
使用者使用其憑證(使用者名稱和密碼)或 JWT 令牌(標頭中)向經過驗證的端點發送請求,並將請求傳遞到身份驗證過濾器
-
AuthenticationFilter(例如,UsernamePasswordAuthenticationFilter):
- 根據提交的憑證(通常以使用者名稱和密碼的形式)處理使用者身份驗證。這就是 UsernamePasswordAuthenticationFilter 發揮作用的地方。
- 它監聽請求,提取使用者名稱和密碼,並將它們傳遞給 AuthenticationManager。
- 但是我們沒有傳遞使用者名稱和密碼,我們只給出令牌,因此在此AuthenticationFilter 之前應該有一個過濾器,它將告訴身份驗證過程使用者已通過身份驗證,無需檢查使用者名稱和密碼,這透過創建JWTFilter 來完成
2.JWT過濾器
這個自訂過濾器擴充了 OncePerRequestFilter 並放置在 UsernamePasswordAuthenticationFilter 之前,它的作用是從請求中提取令牌並驗證它。
如果令牌有效,它會建立一個UsernamePasswordAuthenticationToken 並將該令牌設定到安全性上下文中,告訴Spring Security 該請求已通過身份驗證,並且當此請求傳遞到UsernamePasswordAuthenticationFilter 時,它只是傳遞,因為它具有UsernamePasswordAuthenticationToken
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
如果我們在 UserDetails 類別的幫助下驗證使用者名稱和密碼後傳遞了使用者名稱和密碼而不是令牌,則此 UsernamePasswordAuthenticationToken 是在 AuthenticationManager 和 AuthenticationProvider 的幫助下產生的。
3. 身份驗證管理器
- AuthenticationManager:它接收身份驗證請求並將其委託給我們配置的適當的 AuthenticationProvider。
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
4. 身份驗證提供者
UserDetailsService:AuthenticationProvider 使用 UserDetailsService 根據使用者名稱載入使用者詳細資料。我們為此提供了 UserDetailsService
的實現
憑證驗證:它將提供的密碼與儲存在使用者詳細資料中的密碼進行比較(通常使用密碼編碼器)。
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
現在需要配置所有這些不同的過濾器和 bean,以便 Spring security 知道要做什麼,因此我們建立一個配置類,在其中指定所有配置。
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
到目前為止,我們已經在 Spring Security 的幫助下理解並配置了我們的身份驗證,現在是時候測試它了。
我們將建立一個簡單的應用程序,其中包含兩個控制器 AuthController(處理登入和註冊)和 ProductController(虛擬受保護控制器)
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{ return config.getAuthenticationManager(); }
@Bean public AuthenticationProvider authenticationProvider(){ DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsServiceImpl); authenticationProvider.setPasswordEncoder(passwordEncoder); return authenticationProvider; }
到目前為止,我們已經實現了註冊、登入和驗證,但是如果我還想添加 Login With Google/Github 功能,那麼我們可以藉助 OAuth2.0 來實現
OAuth 2.0
OAuth 2.0 是一種用於授權的協議,透過它,使用者可以授予第三方應用程式存取儲存在其他平台(例如 Google Drive、Github)上的資源的權限,而無需共享這些平台的憑證。
它主要用於啟用社交登錄,例如「使用 google 登入」、「使用 github 登入」。
Google、Facebook、Github 等平台提供了授權伺服器,該伺服器實現了 OAuth 2.0 協議,用於此社交登入或授權存取。
OAuth 2.0 的關鍵概念
資源所有者
客戶
授權伺服器
資源伺服器
訪問令牌
範圍
補助金
現在我們將一一研究每個概念
資源所有者
資源擁有者是想要授權第三方應用程式(您的應用程式)的使用者。
客戶
這是您的(第三方)應用程式想要從資源伺服器存取資料或資源。
資源伺服器
它是儲存用戶資料的伺服器,供第三方應用程式存取。
授權伺服器
對資源擁有者進行驗證並向用戶端(例如 Google 帳戶)頒發存取權杖的伺服器。
訪問令牌
授權伺服器向客戶端頒發的憑證,允許客戶端代表使用者存取資源伺服器。它的生命週期通常很短,很快就會過期,因此也提供了刷新令牌來刷新此存取令牌,以便用戶不需要再次授權。
範圍
使用者授予的特定權限,定義客戶端可以和不能對使用者資料執行什麼操作。例如,對於授權,我們只需要用戶信息,如個人資料、姓名等,但對於文件訪問,需要不同的範圍。
補助金
客戶端應用程式從授權伺服器取得存取權杖的方法。授權定義了授權用戶端應用程式存取資源擁有者的受保護資料的流程和條件。
它是安全的,因為我們不需要向瀏覽器公開我們的客戶端金鑰和其他憑證
OAuth 2.0 提供了兩種最常用的 Grant 類型
-
授權碼授予
它是最常用的授權/方法類型,最安全,並且適用於伺服器端應用程式
在此,授權碼由客戶端提供給後端,後端將存取權杖提供給客戶端。
流程:
- 客戶端將使用者重新導向至授權伺服器。
- 使用者登入並同意。
- 授權伺服器發出授權碼。
- 客戶端與後端交換授權代碼以取得存取權杖。
-
隱性授予
由單頁應用程式 (SPA) 或沒有後端的應用程式使用。在此,存取權杖是在瀏覽器本身中直接產生和頒發的。
流程:
- 客戶端將使用者重新導向至授權伺服器。
- 使用者登入並同意。
- 授權伺服器直接核發存取權杖。
為了完全理解,我們將分別實現兩者,但首先我們將實現我們需要的授權代碼授予
-
授權伺服器
它可以是任何一個平台(例如google 、 github),或者您也可以使用KeyCloak 創建自己的平台,或者也可以遵循OAuth 2.0 標準來建立您自己的平台(我們可能會在下一篇部落格中這樣做?
- Spring Boot 應用
- React 應用程式(前端)
否,當使用者成功登入Google時,授權伺服器(Google的)會將我們的請求重定向到後端端點,我們要做的就是與授權伺服器交換授權程式碼以獲取存取權杖和刷新令牌,然後我們可以根據需要處理身份驗證,最後我們會將回應發送回我們的前端,該前端將有一個cookie 並重定向到我們的儀表板,或者可能是受保護的頁面。
現在我們將研究程式碼,但請確保在 OAuth 用戶端的 Google 控制台儀表板中的授權重定向 URL 中新增後端端點的 URL。
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
就是這樣,它可以正常工作,為了測試,您可以製作一個簡單的前端應用程序,它除了具有上下文之外什麼都沒有,而您不知道登入和註冊功能。
感謝您閱讀這麼久,如果您有任何建議,請在評論中提出
以上是了解 Spring Security 和 OAuth的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

在使用IntelliJIDEAUltimate版本啟動Spring...

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

Redis緩存方案如何實現產品排行榜列表的需求?在開發過程中,我們常常需要處理排行榜的需求,例如展示一個�...

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...
