您好,登錄后才能下訂單哦!
小編給大家分享一下Spring Security有哪些核心組件,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
Spring Security 核心設計
Spring Security 有五個核心組件:SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager。下面分別介紹一下各個組件。
SecurityContext
SecurityContext 即安全上下文,關聯當前用戶的安全信息。用戶通過 Spring Security 的校驗之后,SecurityContext 會存儲驗證信息,下文提到的 Authentication 對象包含當前用戶的身份信息。SecurityContext 的接口簽名如清單 1 所示:
清單 1. SecurityContext 的接口簽名
public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication authentication); }
SecurityContext 存儲在 SecurityContextHolder 中。
SecurityContextHolder
SecurityContextHolder 存儲 SecurityContext 對象。SecurityContextHolder 是一個存儲代理,有三種存儲模式分別是:
MODE_THREADLOCAL:SecurityContext 存儲在線程中。
MODE_INHERITABLETHREADLOCAL:SecurityContext 存儲在線程中,但子線程可以獲取到父線程中的 SecurityContext。
MODE_GLOBAL:SecurityContext 在所有線程中都相同。
SecurityContextHolder 默認使用 MODE_THREADLOCAL 模式,SecurityContext 存儲在當前線程中。調用 SecurityContextHolder 時不需要顯示的參數傳遞,在當前線程中可以直接獲取到 SecurityContextHolder 對象。但是對于很多 C 端的應用(音樂播放器,游戲等等),用戶登錄完畢,在軟件的整個生命周期中只有當前登錄用戶,面對這種情況 SecurityContextHolder 更適合采用 MODE_GLOBAL 模式,SecurityContext 相當于存儲在應用的進程中,SecurityContext 在所有線程中都相同。
Authentication
Authentication 即驗證,表明當前用戶是誰。什么是驗證,比如一組用戶名和密碼就是驗證,當然錯誤的用戶名和密碼也是驗證,只不過 Spring Security 會校驗失敗。Authentication 接口簽名如清單 2 所示:
清單 2. Authentication 的接口簽名
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated); }
Authentication 是一個接口,實現類都會定義 authorities,credentials,details,principal,authenticated 等字段,具體含義如下:
getAuthorities: 獲取用戶權限,一般情況下獲取到的是用戶的角色信息。
getCredentials: 獲取證明用戶認證的信息,通常情況下獲取到的是密碼等信息。
getDetails: 獲取用戶的額外信息,比如 IP 地址、經緯度等。
getPrincipal: 獲取用戶身份信息,在未認證的情況下獲取到的是用戶名,在已認證的情況下獲取到的是 UserDetails (暫時理解為,當前應用用戶對象的擴展)。
isAuthenticated: 獲取當前 Authentication 是否已認證。
setAuthenticated: 設置當前 Authentication 是否已認證。
在驗證前,principal 填充的是用戶名,credentials 填充的是密碼,detail 填充的是用戶的 IP 或者經緯度之類的信息。通過驗證后,Spring Security 對 Authentication 重新注入,principal 填充用戶信息(包含用戶名、年齡等), authorities 會填充用戶的角色信息,authenticated 會被設置為 true。重新注入的 Authentication 會被填充到 SecurityContext 中。
UserDetails
UserDetails 提供 Spring Security 需要的用戶核心信息。UserDetails 的接口簽名如清單 3 所示:
清單 3. UserDetails 的接口簽名
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetails 用 isAccountNonExpired, isAccountNonLocked,isCredentialsNonExpired,isEnabled 表示用戶的狀態(與下文中提到的 DisabledException,LockedException,BadCredentialsException 相對應),具體含義如下:
getAuthorites:獲取用戶權限,本質上是用戶的角色信息。
getPassword: 獲取密碼。
getUserName: 獲取用戶名。
isAccountNonExpired: 賬戶是否過期。
isAccountNonLocked: 賬戶是否被鎖定。
isCredentialsNonExpired: 密碼是否過期。
isEnabled: 賬戶是否可用。
UserDetails 也是一個接口,實現類都會繼承當前應用的用戶信息類,并實現 UserDetails 的接口。假設應用的用戶信息類是 User,自定義的 CustomUserdetails 繼承 User 類并實現 UserDetails 接口。
AuthenticationManager
AuthenticationManager 負責校驗 Authentication 對象。在 AuthenticationManager 的 authenticate 函數中,開發人員實現對 Authentication 的校驗邏輯。如果 authenticate 函數校驗通過,正常返回一個重新注入的 Authentication 對象;校驗失敗,則拋出 AuthenticationException 異常。authenticate 函數簽名如清單 4 所示:
清單 4. authenticate 函數簽名
Authentication authenticate(Authentication authentication)throws AuthenticationException;
AuthenticationManager 可以將異常拋出的更加明確:
當用戶不可用時拋出 DisabledException。
當用戶被鎖定時拋出 LockedException。
當用戶密碼錯誤時拋出 BadCredentialsException。
重新注入的 Authentication 會包含當前用戶的詳細信息,并且被填充到 SecurityContext 中,這樣 Spring Security 的驗證流程就完成了,Spring Security 可以識別到 "你是誰"。
基本校驗流程示例
下面采用 Spring Security 的核心組件寫一個最基本的用戶名密碼校驗示例,如清單 5 所示:
清單 5. Spring Security 核心組件偽代碼
AuthenticationManager amanager = new CustomAuthenticationManager(); Authentication namePwd = new CustomAuthentication(“name”, “password”); try { Authentication result = amanager.authenticate(namePwd); SecurityContextHolder.getContext.setAuthentication(result); } catch(AuthenticationException e) { // TODO 驗證失敗 }
Spring Security 的核心組件易于理解,其基本校驗流程是: 驗證信息傳遞過來,驗證通過,將驗證信息存儲到 SecurityContext 中;驗證失敗,做出相應的處理。
Spring Security 在 Web 中的設計
Spring Security 的一個常見應用場景就是 Web。下面討論 Spring Security 在 Web 中的使用方式。
Spring Security 最簡登錄實例
Spring Security 在 Web 中的使用相對要復雜一點,會涉及到很多組件。現在給出自定義登錄的偽代碼,如清單 6 所示。您可以點擊這里,查看完整的代碼。
清單 6. Web 登錄偽代碼
@Controller public class UserController { @PostMapping(“/login”) public void login(String name, String password){ matchNameAndPassword(name, password); User user = getUser(name); Authentication auth = new CustomAuthentication(user, password); auth.setAuthenticated(true); SecurityContextHolder.getContext.setAuthentication(auth); } }
觀察代碼會發現,如果用 Spring Security 來集成已存在的登錄邏輯,真正和 Spring Security 關聯的代碼只有短短 3 行。驗證邏輯可以不經過 AuthenticationManager,真正需要做的就是把經過驗證的用戶信息注入到 Authentication 中,并將 Authentication 填充到 SecurityContext 中。在實際情況中,登錄邏輯的確可以這樣寫,尤其是已經存在登錄邏輯的時候,通常會這樣寫。這樣寫雖然方便,但是不符合 Spring Security 在 Web 中的架構設計。
下面視頻中會介紹已存在的項目如何與 Spring Security 進行集成,要求對已存在的登錄驗證邏輯不變,但可以使用 Spring Security 的優秀特性和功能。
視頻地址:https://mediacenter.ibm.com/media/0_qvbohipj
項目地址:https://github.com/springAppl/yuchigong
Spring Security 在 Web 中的核心組件
下面介紹在 Web 環境中 Spring Security 的核心組件。
FilterChainProxy
FilterChaniProxy 是 FilterChain 代理。FilterChain 維護了一個 Filter 隊列,這些 Filter 為 Spring Security 提供了強大的功能。一個很常見的問題是:Spring Security 在 Web 中的入口是哪里?答案是 Filter。Spring Security 在 Filter 中創建 Authentication 對象,并調用 AuthenticationManager 進行校驗。Spring Security 選擇 Filter,而沒有采用上文中 Controller 的方式有以下優點。Spring Security 依賴 J2EE 標準,無需依賴特定的 MVC 框架。另一方面 Spring MVC 通過 Servlet 做請求轉發,如果 Spring Security 采用 Servlet,那么 Spring Security 和 Spring MVC 的集成會存在問題。FilterChain 維護了很多 Filter,每個 Filter 都有自己的功能,因此在 Spring Security 中添加新功能時,推薦通過 Filter 的方式來實現。
ProviderManager
ProviderManager 是 AuthenticationManager 的實現類。ProviderManager 并沒有實現對 Authentication 的校驗功能,而是采用代理模式將校驗功能交給 AuthenticationProvider 去實現。這樣設計是因為在 Web 環境中可能會支持多種不同的驗證方式,比如用戶名密碼登錄、短信登錄、指紋登錄等等,如果每種驗證方式的代碼都寫在 ProviderManager 中,想想都是災難。因此為每種驗證方式提供對應的 AuthenticationProvider,ProviderManager 將驗證任務代理給對應的 AuthenticationProvider,這是一種不錯的解決方案。在 ProviderManager 中可以找到以下代碼,如清單 7 所示:
清單 7. ProviderManager 代碼片段
private List<AuthenticationProvider> providers; public Authentication authenticate(Authentication authentication) throws AuthenticationException { ...... for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } } }
ProviderManager 維護了一個 AuthenticationProvider 隊列。當 Authentication 傳遞進來時,ProviderManager 通過 supports 函數查找支持校驗的 AuthenticationProvider。如果沒有找到支持的 AuthenticationProvider 將拋出 ProviderNotFoundException 異常。
AuthenticationProvider
AuthenticationProvider 是在 Web 環境中真正對 Authentication 進行校驗的組件。其接口簽名如清單 8 所示:
清單 8. AuthenticationProvider 的接口簽名
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); }
其中,authenticate 函數用于校驗 Authentication 對象;supports 函數用于判斷 provider 是否支持校驗 Authentication 對象。
當應用添加新的驗證方式時,驗證邏輯需要寫在對應 AuthenticationProvider 中的 authenticate 函數中。驗證通過返回一個重新注入的 Authentication,驗證失敗拋出 AuthenticationException 異常。
Spring Security 在 Web 中的認證示例
下面的視頻中會介紹采用 Spring Security 提供的 UsernamePasswordAuthenticationFilter 實現登錄驗證。
視頻地址:https://mediacenter.ibm.com/media/0_7vq75cue
項目地址:https://github.com/springAppl/rachel
下面以用戶名密碼登錄為例來梳理 Spring Security 在 Web 中的認證流程。上文提到 Spring Security 是以 Filter 來作為校驗的入口點。在用戶名密碼登錄中對應的 Filter 是 UsernamePasswordAuthenticationFilter。attemptAuthentication 函數會執行調用校驗的邏輯。在 attemptAuthentication 函數中,可以找到以下代碼,如清單 9 所示:
清單 9. attemptAuthentication 函數代碼片段
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException { ...... UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
attemptAuthentication 函數會調用 AuthenticationManager 執行校驗邏輯,并獲取到重新注入后的 Authentication。在 UsernamePasswordAuthenticationFilter 父類 AbstractAuthenticationProcessingFilter 的 successfulAuthentication 函數中發現以下代碼,如清單 10 所示:
清單 10. successAuthentication 函數
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException { ...... SecurityContextHolder.getContext().setAuthentication(authResult); ...... }
successfulAuthentication 函數會把重新注入的 Authentication 填充到 SecurityContext 中,完成驗證。
在 Web 中,AuthenticationManager 的實現類 ProviderManager 并沒有實現校驗邏輯,而是代理給 AuthenticationProvider, 在用戶名密碼登錄中就是 DaoAuthenticationProvider。DaoAuthenticationProvider 主要完成 3 個功能:獲取 UserDetails、校驗密碼、重新注入 Authentication。在 authenticate 函數中發現以下代碼,如清單 11 所示:
清單 11. DaoAuthenticationProvider.authenticate 函數簽名
public Authentication authenticate(Authentication authentication) throws AuthenticationException { ...... // 獲取 UserDetails UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } ...... } ...... try { ...... //校驗密碼 additionalAuthenticationChecks( user, (UsernamePasswordAuthenticationToken) authentication ); } ...... // 從新注入 Authentication return createSuccessAuthentication( principalToReturn, authentication, user ); }
首先從 userCache 緩存中查找 UserDetails, 如果緩存中沒有獲取到,調用 retrieveUser 函數獲取 UserDetails。retrieveUser 函數簽名如清單 12 所示:
清單 12. retrieveUser 函數簽名
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } ...... return loadedUser; }
retrieveUser 函數調用 UserDetailsService 獲取 UserDetails 對象。UserDetailsService 接口簽名如清單 13 所示:
清單 13. UserDetailsService 接口簽名
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
UserDetailsService 非常簡單,只有一個 loadUserByUserName 函數,函數參數雖然名為 username,但只要是用戶的唯一標識符即可。下面是基于數據庫存儲的簡單示例, 如清單 14 所示:
清單 14. CustomUserDetailsService 類簽名
public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.findByName(username); if(Objects.isNull(user)) { throw new UsernameNotFoundException(); } UserDetails details = new CustomUserDetails(user); return details; } }
調用 UserDao 獲取 User 對象,將 User 對象包裝成 UserDetails 對象。如果沒有找到 User 對象,需要拋出 UsernameNotFoundException 異常。
DaoAuthenticationProvider 密碼校驗調用 additionalAuthenticationChecks 函數,具體通過 PasswordEncoder 比對用戶輸入的密碼和存儲在應用中的密碼是否相等,如果不相等,拋出 BadCredentialsException 異常。
DaoAuthenticationProvider 對 Authentication 對象的重新注入通過調用 createSuccessAuthentication 函數, 如清單 15 所示:
清單 15. createSuccessAuthentication 函數簽名
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()) ); result.setDetails(authentication.getDetails()); return result; }
以上就是 Spring Security 在 Web 環境中對于用戶名密碼校驗的整個流程,簡言之:
UsernamePasswordAuthenticationFilter 接受用戶名密碼登錄請求,將 Authentication 傳遞給 ProviderManager 進行校驗。
ProviderManager 將校驗任務代理給 DaoAuthenticationProvider。
DaoAuthenticationProvider 對 Authentication 的用戶名和密碼進行校驗,校驗通過后返回重新注入的 Authentication 對象。
UsernamePasswordAuthenticationFilter 將重新注入的 Authentication 對象填充到 SecurityContext 中。
指紋登錄實踐
指紋登錄和用戶名密碼登錄區別很小,只是將密碼換成了指紋特征值。下面采用 Spring Security 推薦寫法 Filter-AuthenticationProvider 的形式來定義相關組件以實現指紋登錄。完整的項目地址:https://github.com/springAppl/rachel。
FingerPrintToken
FingerPrintToken 增加 name 和 fingerPrint 字段,分別代表用戶名和指紋特征值,如清單 16 所示:
清單 16. FingerPrintToken 函數簽名
public class FingerPrintToken implements Authentication { private String name; private String fingerPrint; ...... }
FingerPrintFilter
FingerPrintFilter 處理指紋登錄請求,調用 AuthenticationManager 進行驗證,驗證通過后調用 SecurityContextHolder 將重新注入的 Authentication 填充到 SecurityContext 中,如清單 17 所示:
清單 17. doFilter 函數簽名
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (Objects.equals(httpServletRequest.getRequestURI(), "/api/finger-print")) { // 調用 AuthenticationManager, 并填充 SecurityContext } }
FingerPrintProvider
FingerPrintProvider 負責處理 FingerPrintToken,需要在 supports 函數中支持處理 FingerPrintToken。authenticate 函數負責 UserDetails 獲取,指紋校驗,FingerPrintToken 的重新注入。
FingerPrintUserDetails
FingerPrintUserDetails 繼承 User 并實現 UserDetails 的方法,應用的用戶信息可以加載到 Spring Security 中使用。
FingerPrintUserDetailsService
FingerPrintUserDetailsService 獲取 FingerUserDetails。通過 UserDao 查找到 User,并將 User 轉換為 Spring Security 可識別 UserDetails。
SecurityConfig
SecurityConfig 繼承 WebSecurityConfigurerAdapter,需要定義 Spring Security 配置類。Spring Security 的配置不是本文的重點,配置時只需要注意以下幾點:
將 FingerPrintFilter、FingerPrintProvider 添加進去。
將 FingerPrintFilter 的執行順序放置在 SecurityContextPersistenceFilter 之后即可。Spring Security 維護了一個 Filter 的 list,因此每個 Filter 是有順序的。
將 "/api/test" 請求設置為用戶驗證成功后才允許方問。
配置代碼在 configure 函數中,如清單 18 所示:
清單 18. configure 函數
protected void configure(HttpSecurity http) throws Exception { http .userDetailsService(userDetailsService()) .addFilterAfter(fingerPrintFilter(), SecurityContextPersistenceFilter.class) .authenticationProvider(fingerPrintProvider()) .authorizeRequests() .mvcMatchers(HttpMethod.GET, "/api/test").authenticated() }
以上是“Spring Security有哪些核心組件”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。