javascript
Spring security (一)架构框架-Component、Service、Filter分析
??想要深入spring security的authentication (身份驗證)和access-control(訪問權限控制)工作流程,必須清楚spring security的主要技術點包括關鍵接口、類以及抽象類如何協同工作進行authentication 和access-control的實現。
1.spring security 認證和授權流程
常見認證和授權流程可以分成:
1.1 spring security 認證
上述前三點為spring security認證驗證環節:
1.2 spring security訪問授權
根據上述描述的過程,我們接下來主要去分析其中涉及的一下Component、Service、Filter。
2.核心組件(Core Component )
2.1 SecurityContextHolder
??SecurityContextHolder提供對SecurityContext的訪問,存儲security context(用戶信息、角色權限等),而且其具有下列儲存策略即工作模式:
SecurityContextHolder.MODE_THREADLOCAL(默認):使用ThreadLocal,信息可供此線程下的所有的方法使用,一種與線程綁定的策略,此天然很適合Servlet Web應用。
SecurityContextHolder.MODE_GLOBAL:使用于獨立應用
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具有相同安全標示的線程
修改SecurityContextHolder的工作模式有兩種方法 :
在默認ThreadLocal策略中,SecurityContextHolder為靜態方法獲取用戶信息為:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername();} else {String username = principal.toString();} 復制代碼但是一般不需要自身去獲取。 其中getAuthentication()返回一個Authentication認證主體,接下來分析Authentication、UserDetails細節。
2.2 Authentication
??Spring Security使用一個Authentication對象來描述當前用戶的相關信息,其包含用戶擁有的權限信息列表、用戶細節信息(身份信息、認證信息)。Authentication為認證主體在spring security中時最高級別身份/認證的抽象,常見的實現類UsernamePasswordAuthenticationToken。Authentication接口源碼:
public interface Authentication extends Principal, Serializable { //權限信息列表,默認GrantedAuthority接口的一些實現類Collection<? extends GrantedAuthority> getAuthorities(); //密碼信息Object getCredentials();//細節信息,web應用中的實現接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值Object getDetails();//通常返回值為UserDetails實現類Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean var1) throws IllegalArgumentException; } 復制代碼前面兩個組件都涉及了UserDetails,以及GrantedAuthority其到底是什么呢?2.3小節分析。
2.3 UserDetails&GrantedAuthority
??UserDetails提供從應用程序的DAO或其他安全數據源構建Authentication對象所需的信息,包含GrantedAuthority。其官方實現類為User,開發者可以實現其接口自定義UserDetails實現類。其接口源碼:
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled(); } 復制代碼??UserDetails與Authentication接口功能類似,其實含義即是Authentication為用戶提交的認證憑證(賬號密碼),UserDetails為系統中用戶正確認證憑證,在UserDetailsService中的loadUserByUsername方法獲取正確的認證憑證。 ??其中在getAuthorities()方法中獲取到GrantedAuthority列表是代表用戶訪問應用程序權限范圍,此類權限通常是“role(角色)”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。GrantedAuthority接口常見的實現類SimpleGrantedAuthority。
3. 核心服務類(Core Services)
3.1 AuthenticationManager、ProviderManager以及AuthenticationProvider
??AuthenticationManager是認證相關的核心接口,是認證一切的起點。但常見的認證流程都是AuthenticationManager實現類ProviderManager處理,而且ProviderManager實現類基于委托者模式維護AuthenticationProvider 列表用于不同的認證方式。例如:
??AuthenticationProvider為
ProviderManager源碼分析:
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;//AuthenticationProvider列表依次認證for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}try {//每個AuthenticationProvider進行認證result = provider.authenticate(authentication)if (result != null) {copyDetails(authentication, result);break;}}....catch (AuthenticationException e) {lastException = e;}}//進行父類AuthenticationProvider進行認證if (result == null && parent != null) {// Allow the parent to try.try {result = parent.authenticate(authentication);}catch (AuthenticationException e) {lastException = e;}}// 如果有Authentication信息,則直接返回if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {//清除密碼((CredentialsContainer) result).eraseCredentials();}//發布登錄成功事件eventPublisher.publishAuthenticationSuccess(result);return result;}//如果都沒認證成功,拋出異常if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}prepareException(lastException, authentication);throw lastException;} 復制代碼??ProviderManager 中的AuthenticationProvider列表,會依照次序去認證,默認策略下,只需要通過一個AuthenticationProvider的認證,即可被認為是登錄成功,而且AuthenticationProvider認證成功后返回一個Authentication實體,并為了安全會進行清除密碼。如果所有認證器都無法認證成功,則ProviderManager 會拋出一個ProviderNotFoundException異常。
3.2 UserDetailsService
??UserDetailsService接口作用是從特定的地方獲取認證的數據源(賬號、密碼)。如何獲取到系統中正確的認證憑證,通過loadUserByUsername(String username)獲取認證信息,而且其只有一個方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; 復制代碼其常見的實現類從數據獲取的JdbcDaoImpl實現類,從內存中獲取的InMemoryUserDetailsManager實現類,不過我們可以實現其接口自定義UserDetailsService實現類,如下:
public class CustomUserService implements UserDetailsService {@Autowired//用戶mapperprivate UserInfoMapper userInfoMapper;@Autowired//用戶權限mapperprivate PermissionInfoMapper permissionInfoMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserInfoDTO userInfo = userInfoMapper.getUserInfoByUserName(username);if (userInfo != null) {List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();//組裝權限GrantedAuthority objectfor (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionInfoDTO.getPermissionName());grantedAuthorityList.add(grantedAuthority);}}//返回用戶信息return new User(userInfo.getUserName(), userInfo.getPasswaord(), grantedAuthorityList);}else {//拋出用戶不存在異常throw new UsernameNotFoundException("admin" + username + "do not exist");}} } 復制代碼3.3 AccessDecisionManager&SecurityMetadataSource
??AccessDecisionManager是由AbstractSecurityInterceptor調用,負責做出最終的訪問控制決策。
AccessDecisionManager接口源碼:
//訪問控制決策void decide(Authentication authentication, Object secureObject,Collection<ConfigAttribute> attrs) throws AccessDeniedException;//是否支持處理傳遞的ConfigAttributeboolean supports(ConfigAttribute attribute);//確認class是否為AccessDecisionManagerboolean supports(Class clazz); 復制代碼??SecurityMetadataSource包含著AbstractSecurityInterceptor訪問授權所需的元數據(動態url、動態授權所需的數據),在AbstractSecurityInterceptor授權模塊中結合AccessDecisionManager進行訪問授權。其涉及了ConfigAttribute。 SecurityMetadataSource接口:
Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException;Collection<ConfigAttribute> getAllConfigAttributes();boolean supports(Class<?> clazz); 復制代碼我們還可以自定義SecurityMetadataSource數據源,實現接口FilterInvocationSecurityMetadataSource。例:
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {public List<ConfigAttribute> getAttributes(Object object) {FilterInvocation fi = (FilterInvocation) object;String url = fi.getRequestUrl();String httpMethod = fi.getRequest().getMethod();List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();// Lookup your database (or other source) using this information and populate the// list of attributesreturn attributes;}public Collection<ConfigAttribute> getAllConfigAttributes() {return null;}public boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);} } 復制代碼3.4 PasswordEncoder
??為了存儲安全,一般要對密碼進行算法加密,而spring security提供了加密PasswordEncoder接口。其實現類有使用BCrypt hash算法實現的BCryptPasswordEncoder,SCrypt hashing 算法實現的SCryptPasswordEncoder實現類,實現類內部實現可看源碼分析。而PasswordEncoder接口只有兩個方法:
public interface PasswordEncoder {//密碼加密String encode(CharSequence rawPassword);//密碼配對boolean matches(CharSequence rawPassword, String encodedPassword); } 復制代碼4 核心 Security 過濾器(Core Security Filters)
4.1 FilterSecurityInterceptor
??FilterSecurityInterceptor是Spring security授權模塊入口,該類根據訪問的用戶的角色,權限授權訪問那些資源(訪問特定路徑應該具備的權限)。
??FilterSecurityInterceptor封裝FilterInvocation對象進行操作,所有的請求到了這一個filter,如果這個filter之前沒有執行過的話,那么首先執行其父類AbstractSecurityInterceptor提供的InterceptorStatusToken token = super.beforeInvocation(fi),在此方法中使用AuthenticationManager獲取Authentication中用戶詳情,使用ConfigAttribute封裝已定義好訪問權限詳情,并使用AccessDecisionManager.decide()方法進行訪問權限控制。
FilterSecurityInterceptor源碼分析:
AbstractSecurityInterceptor源碼分析:
protected InterceptorStatusToken beforeInvocation(Object object) {....//獲取所有訪問權限(url-role)屬性列表(已定義在數據庫或者其他地方)Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);....//獲取該用戶訪問信息(包括url,訪問權限)Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {//進行授權訪問this.accessDecisionManager.decide(authenticated, object, attributes);}catch.... } 復制代碼4.2 UsernamePasswordAuthenticationFilter
??UsernamePasswordAuthenticationFilter使用username和password表單登錄使用的過濾器,也是最為常用的過濾器。其源碼:
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {//獲取表單中的用戶名和密碼String username = obtainUsername(request);String password = obtainPassword(request);...username = username.trim();//組裝成username+password形式的tokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);//交給內部的AuthenticationManager去認證,并返回認證信息return this.getAuthenticationManager().authenticate(authRequest); } 復制代碼??其主要代碼為創建UsernamePasswordAuthenticationToken的Authentication實體以及調用AuthenticationManager進行authenticate認證,根據認證結果執行successfulAuthentication或者unsuccessfulAuthentication,無論成功失敗,一般的實現都是轉發或者重定向等處理,不再細究AuthenticationSuccessHandler和AuthenticationFailureHandle。興趣的可以研究一下其父類AbstractAuthenticationProcessingFilter過濾器。
4.3 AnonymousAuthenticationFilter
AnonymousAuthenticationFilter是匿名登錄過濾器,它位于常用的身份認證過濾器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)之后,意味著只有在上述身份過濾器執行完畢后,SecurityContext依舊沒有用戶信息,AnonymousAuthenticationFilter該過濾器才會有意義——基于用戶一個匿名身份。 AnonymousAuthenticationFilter源碼分析:
public class AnonymousAuthenticationFilter extends GenericFilterBean implementsInitializingBean {...public AnonymousAuthenticationFilter(String key) {this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));}...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {if (SecurityContextHolder.getContext().getAuthentication() == null) {//創建匿名登錄Authentication的信息SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));...}chain.doFilter(req, res);}//創建匿名登錄Authentication的信息方法protected Authentication createAuthentication(HttpServletRequest request) {AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,principal, authorities);auth.setDetails(authenticationDetailsSource.buildDetails(request));return auth;} } 復制代碼4.4 SecurityContextPersistenceFilter
??SecurityContextPersistenceFilter的兩個主要作用便是request來臨時,創建SecurityContext安全上下文信息和request結束時清空SecurityContextHolder。源碼后續分析。
小節總結:
. AbstractAuthenticationProcessingFilter:主要處理登錄
. FilterSecurityInterceptor:主要處理鑒權
總結
??經過上面對核心的Component、Service、Filter分析,初步了解了Spring Security工作原理以及認證和授權工作流程。Spring Security認證和授權還有很多負責的過程需要深入了解,所以下次會對認證模塊和授權模塊進行更具體工作流程分析以及案例呈現。最后以上純粹個人結合博客和官方文檔總結,如有錯請指出!
轉載于:https://juejin.im/post/5d074dc1f265da1bce3dd10f
總結
以上是生活随笔為你收集整理的Spring security (一)架构框架-Component、Service、Filter分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux ntfs硬盘自动挂,linu
- 下一篇: 可添加至收藏夹并在浏览器地址栏运行的JS