javascript
Spring security (一)架构框架-Component、Service、Filter分析
??想要深入spring security的authentication (身份驗(yàn)證)和access-control(訪問權(quán)限控制)工作流程,必須清楚spring security的主要技術(shù)點(diǎn)包括關(guān)鍵接口、類以及抽象類如何協(xié)同工作進(jìn)行authentication 和access-control的實(shí)現(xiàn)。
1.spring security 認(rèn)證和授權(quán)流程
常見認(rèn)證和授權(quán)流程可以分成:
1.1 spring security 認(rèn)證
上述前三點(diǎn)為spring security認(rèn)證驗(yàn)證環(huán)節(jié):
1.2 spring security訪問授權(quán)
根據(jù)上述描述的過程,我們接下來主要去分析其中涉及的一下Component、Service、Filter。
2.核心組件(Core Component )
2.1 SecurityContextHolder
??SecurityContextHolder提供對SecurityContext的訪問,存儲(chǔ)security context(用戶信息、角色權(quán)限等),而且其具有下列儲(chǔ)存策略即工作模式:
SecurityContextHolder.MODE_THREADLOCAL(默認(rèn)):使用ThreadLocal,信息可供此線程下的所有的方法使用,一種與線程綁定的策略,此天然很適合Servlet Web應(yīng)用。
SecurityContextHolder.MODE_GLOBAL:使用于獨(dú)立應(yīng)用
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具有相同安全標(biāo)示的線程
修改SecurityContextHolder的工作模式有兩種方法 :
在默認(rèn)ThreadLocal策略中,SecurityContextHolder為靜態(tài)方法獲取用戶信息為:
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();} 復(fù)制代碼但是一般不需要自身去獲取。 其中g(shù)etAuthentication()返回一個(gè)Authentication認(rèn)證主體,接下來分析Authentication、UserDetails細(xì)節(jié)。
2.2 Authentication
??Spring Security使用一個(gè)Authentication對象來描述當(dāng)前用戶的相關(guān)信息,其包含用戶擁有的權(quán)限信息列表、用戶細(xì)節(jié)信息(身份信息、認(rèn)證信息)。Authentication為認(rèn)證主體在spring security中時(shí)最高級(jí)別身份/認(rèn)證的抽象,常見的實(shí)現(xiàn)類UsernamePasswordAuthenticationToken。Authentication接口源碼:
public interface Authentication extends Principal, Serializable { //權(quán)限信息列表,默認(rèn)GrantedAuthority接口的一些實(shí)現(xiàn)類Collection<? extends GrantedAuthority> getAuthorities(); //密碼信息Object getCredentials();//細(xì)節(jié)信息,web應(yīng)用中的實(shí)現(xiàn)接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值Object getDetails();//通常返回值為UserDetails實(shí)現(xiàn)類Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean var1) throws IllegalArgumentException; } 復(fù)制代碼前面兩個(gè)組件都涉及了UserDetails,以及GrantedAuthority其到底是什么呢?2.3小節(jié)分析。
2.3 UserDetails&GrantedAuthority
??UserDetails提供從應(yīng)用程序的DAO或其他安全數(shù)據(jù)源構(gòu)建Authentication對象所需的信息,包含GrantedAuthority。其官方實(shí)現(xiàn)類為User,開發(fā)者可以實(shí)現(xiàn)其接口自定義UserDetails實(shí)現(xiàn)類。其接口源碼:
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled(); } 復(fù)制代碼??UserDetails與Authentication接口功能類似,其實(shí)含義即是Authentication為用戶提交的認(rèn)證憑證(賬號(hào)密碼),UserDetails為系統(tǒng)中用戶正確認(rèn)證憑證,在UserDetailsService中的loadUserByUsername方法獲取正確的認(rèn)證憑證。 ??其中在getAuthorities()方法中獲取到GrantedAuthority列表是代表用戶訪問應(yīng)用程序權(quán)限范圍,此類權(quán)限通常是“role(角色)”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。GrantedAuthority接口常見的實(shí)現(xiàn)類SimpleGrantedAuthority。
3. 核心服務(wù)類(Core Services)
3.1 AuthenticationManager、ProviderManager以及AuthenticationProvider
??AuthenticationManager是認(rèn)證相關(guān)的核心接口,是認(rèn)證一切的起點(diǎn)。但常見的認(rèn)證流程都是AuthenticationManager實(shí)現(xiàn)類ProviderManager處理,而且ProviderManager實(shí)現(xiàn)類基于委托者模式維護(hù)AuthenticationProvider 列表用于不同的認(rèn)證方式。例如:
??AuthenticationProvider為
ProviderManager源碼分析:
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;//AuthenticationProvider列表依次認(rèn)證for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}try {//每個(gè)AuthenticationProvider進(jìn)行認(rèn)證result = provider.authenticate(authentication)if (result != null) {copyDetails(authentication, result);break;}}....catch (AuthenticationException e) {lastException = e;}}//進(jìn)行父類AuthenticationProvider進(jìn)行認(rèn)證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();}//發(fā)布登錄成功事件eventPublisher.publishAuthenticationSuccess(result);return result;}//如果都沒認(rèn)證成功,拋出異常if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}prepareException(lastException, authentication);throw lastException;} 復(fù)制代碼??ProviderManager 中的AuthenticationProvider列表,會(huì)依照次序去認(rèn)證,默認(rèn)策略下,只需要通過一個(gè)AuthenticationProvider的認(rèn)證,即可被認(rèn)為是登錄成功,而且AuthenticationProvider認(rèn)證成功后返回一個(gè)Authentication實(shí)體,并為了安全會(huì)進(jìn)行清除密碼。如果所有認(rèn)證器都無法認(rèn)證成功,則ProviderManager 會(huì)拋出一個(gè)ProviderNotFoundException異常。
3.2 UserDetailsService
??UserDetailsService接口作用是從特定的地方獲取認(rèn)證的數(shù)據(jù)源(賬號(hào)、密碼)。如何獲取到系統(tǒng)中正確的認(rèn)證憑證,通過loadUserByUsername(String username)獲取認(rèn)證信息,而且其只有一個(gè)方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; 復(fù)制代碼其常見的實(shí)現(xiàn)類從數(shù)據(jù)獲取的JdbcDaoImpl實(shí)現(xiàn)類,從內(nèi)存中獲取的InMemoryUserDetailsManager實(shí)現(xiàn)類,不過我們可以實(shí)現(xiàn)其接口自定義UserDetailsService實(shí)現(xiàn)類,如下:
public class CustomUserService implements UserDetailsService {@Autowired//用戶mapperprivate UserInfoMapper userInfoMapper;@Autowired//用戶權(quán)限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<>();//組裝權(quán)限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");}} } 復(fù)制代碼3.3 AccessDecisionManager&SecurityMetadataSource
??AccessDecisionManager是由AbstractSecurityInterceptor調(diào)用,負(fù)責(zé)做出最終的訪問控制決策。
AccessDecisionManager接口源碼:
//訪問控制決策void decide(Authentication authentication, Object secureObject,Collection<ConfigAttribute> attrs) throws AccessDeniedException;//是否支持處理傳遞的ConfigAttributeboolean supports(ConfigAttribute attribute);//確認(rèn)class是否為AccessDecisionManagerboolean supports(Class clazz); 復(fù)制代碼??SecurityMetadataSource包含著AbstractSecurityInterceptor訪問授權(quán)所需的元數(shù)據(jù)(動(dòng)態(tài)url、動(dòng)態(tài)授權(quán)所需的數(shù)據(jù)),在AbstractSecurityInterceptor授權(quán)模塊中結(jié)合AccessDecisionManager進(jìn)行訪問授權(quán)。其涉及了ConfigAttribute。 SecurityMetadataSource接口:
Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException;Collection<ConfigAttribute> getAllConfigAttributes();boolean supports(Class<?> clazz); 復(fù)制代碼我們還可以自定義SecurityMetadataSource數(shù)據(jù)源,實(shí)現(xiàn)接口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);} } 復(fù)制代碼3.4 PasswordEncoder
??為了存儲(chǔ)安全,一般要對密碼進(jìn)行算法加密,而spring security提供了加密PasswordEncoder接口。其實(shí)現(xiàn)類有使用BCrypt hash算法實(shí)現(xiàn)的BCryptPasswordEncoder,SCrypt hashing 算法實(shí)現(xiàn)的SCryptPasswordEncoder實(shí)現(xiàn)類,實(shí)現(xiàn)類內(nèi)部實(shí)現(xiàn)可看源碼分析。而PasswordEncoder接口只有兩個(gè)方法:
public interface PasswordEncoder {//密碼加密String encode(CharSequence rawPassword);//密碼配對boolean matches(CharSequence rawPassword, String encodedPassword); } 復(fù)制代碼4 核心 Security 過濾器(Core Security Filters)
4.1 FilterSecurityInterceptor
??FilterSecurityInterceptor是Spring security授權(quán)模塊入口,該類根據(jù)訪問的用戶的角色,權(quán)限授權(quán)訪問那些資源(訪問特定路徑應(yīng)該具備的權(quán)限)。
??FilterSecurityInterceptor封裝FilterInvocation對象進(jìn)行操作,所有的請求到了這一個(gè)filter,如果這個(gè)filter之前沒有執(zhí)行過的話,那么首先執(zhí)行其父類AbstractSecurityInterceptor提供的InterceptorStatusToken token = super.beforeInvocation(fi),在此方法中使用AuthenticationManager獲取Authentication中用戶詳情,使用ConfigAttribute封裝已定義好訪問權(quán)限詳情,并使用AccessDecisionManager.decide()方法進(jìn)行訪問權(quán)限控制。
FilterSecurityInterceptor源碼分析:
AbstractSecurityInterceptor源碼分析:
protected InterceptorStatusToken beforeInvocation(Object object) {....//獲取所有訪問權(quán)限(url-role)屬性列表(已定義在數(shù)據(jù)庫或者其他地方)Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);....//獲取該用戶訪問信息(包括url,訪問權(quán)限)Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {//進(jìn)行授權(quán)訪問this.accessDecisionManager.decide(authenticated, object, attributes);}catch.... } 復(fù)制代碼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);//交給內(nèi)部的AuthenticationManager去認(rèn)證,并返回認(rèn)證信息return this.getAuthenticationManager().authenticate(authRequest); } 復(fù)制代碼??其主要代碼為創(chuàng)建UsernamePasswordAuthenticationToken的Authentication實(shí)體以及調(diào)用AuthenticationManager進(jìn)行authenticate認(rèn)證,根據(jù)認(rèn)證結(jié)果執(zhí)行successfulAuthentication或者unsuccessfulAuthentication,無論成功失敗,一般的實(shí)現(xiàn)都是轉(zhuǎn)發(fā)或者重定向等處理,不再細(xì)究AuthenticationSuccessHandler和AuthenticationFailureHandle。興趣的可以研究一下其父類AbstractAuthenticationProcessingFilter過濾器。
4.3 AnonymousAuthenticationFilter
AnonymousAuthenticationFilter是匿名登錄過濾器,它位于常用的身份認(rèn)證過濾器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)之后,意味著只有在上述身份過濾器執(zhí)行完畢后,SecurityContext依舊沒有用戶信息,AnonymousAuthenticationFilter該過濾器才會(huì)有意義——基于用戶一個(gè)匿名身份。 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) {//創(chuàng)建匿名登錄Authentication的信息SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));...}chain.doFilter(req, res);}//創(chuàng)建匿名登錄Authentication的信息方法protected Authentication createAuthentication(HttpServletRequest request) {AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,principal, authorities);auth.setDetails(authenticationDetailsSource.buildDetails(request));return auth;} } 復(fù)制代碼4.4 SecurityContextPersistenceFilter
??SecurityContextPersistenceFilter的兩個(gè)主要作用便是request來臨時(shí),創(chuàng)建SecurityContext安全上下文信息和request結(jié)束時(shí)清空SecurityContextHolder。源碼后續(xù)分析。
小節(jié)總結(jié):
. AbstractAuthenticationProcessingFilter:主要處理登錄
. FilterSecurityInterceptor:主要處理鑒權(quán)
總結(jié)
??經(jīng)過上面對核心的Component、Service、Filter分析,初步了解了Spring Security工作原理以及認(rèn)證和授權(quán)工作流程。Spring Security認(rèn)證和授權(quán)還有很多負(fù)責(zé)的過程需要深入了解,所以下次會(huì)對認(rèn)證模塊和授權(quán)模塊進(jìn)行更具體工作流程分析以及案例呈現(xiàn)。最后以上純粹個(gè)人結(jié)合博客和官方文檔總結(jié),如有錯(cuò)請指出!
轉(zhuǎn)載于:https://juejin.im/post/5d074dc1f265da1bce3dd10f
總結(jié)
以上是生活随笔為你收集整理的Spring security (一)架构框架-Component、Service、Filter分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux ntfs硬盘自动挂,linu
- 下一篇: java简历项目经验范文