Spring security (一)架構框架-Component、Service、Filter分析

  想要深刻spring security的authentication (身份驗證)和access-control(訪問權限控制)工做流程,必須清楚spring security的主要技術點包括關鍵接口、類以及抽象類如何協同工做進行authentication 和access-control的實現。web

1.spring security 認證和受權流程

常見認證和受權流程能夠分紅:算法

  1. A user is prompted to log in with a username and password (用戶用帳密碼登陸)
  2. The system (successfully) verifies that the password is correct for the username(校驗密碼正確性)
  3. The context information for that user is obtained (their list of roles and so on).(獲取用戶信息context,如權限)
  4. A security context is established for the user(爲用戶建立security context)
  5. The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.(訪問權限控制,是否具備訪問權限)

1.1 spring security 認證

上述前三點爲spring security認證驗證環節:spring

  1. 一般經過AbstractAuthenticationProcessingFilter過濾器將帳號密碼組裝成Authentication實現類UsernamePasswordAuthenticationToken;
  2. 將token傳遞給AuthenticationManager驗證是否有效,而AuthenticationManager一般使用ProviderManager實現類來檢驗;
  3. AuthenticationManager認證成功後將返回一個擁有詳細信息的Authentication object(包括權限信息,身份信息,細節信息,但密碼一般會被移除);
  4. 經過SecurityContextHolder.getContext().getAuthentication().getPrincipal()將Authentication設置到security context中。

1.2 spring security訪問受權

  1. 經過FilterSecurityInterceptor過濾器入口進入;
  2. FilterSecurityInterceptor經過其繼承的抽象類的AbstractSecurityInterceptor.beforeInvocation(Object object)方法進行訪問受權,其中涉及了類AuthenticationManager、AccessDecisionManager、SecurityMetadataSource等。

根據上述描述的過程,咱們接下來主要去分析其中涉及的一下Component、Service、Filter。數據庫

2.核心組件(Core Component )

2.1 SecurityContextHolder

  SecurityContextHolder提供對SecurityContext的訪問,存儲security context(用戶信息、角色權限等),並且其具備下列儲存策略即工做模式:安全

  1. SecurityContextHolder.MODE_THREADLOCAL(默認):使用ThreadLocal,信息可供此線程下的全部的方法使用,一種與線程綁定的策略,此自然很適合Servlet Web應用。cookie

  2. SecurityContextHolder.MODE_GLOBAL:使用於獨立應用session

  3. SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具備相同安全標示的線程app

修改SecurityContextHolder的工做模式有兩種方法 :ide

  1. 設置一個系統屬性(system.properties) : spring.security.strategy;
  2. 調用SecurityContextHolder靜態方法setStrategyName()

在默認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 列表用於不一樣的認證方式。例如:

  1. 使用帳號密碼認證方式DaoAuthenticationProvider實現類(繼承了AbstractUserDetailsAuthenticationProvide抽象類),其爲默認認證方式,進行數據庫庫獲取認證數據信息。
  2. 遊客身份登陸認證方式AnonymousAuthenticationProvider實現類
  3. 從cookies獲取認證方式RememberMeAuthenticationProvider實現類

  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
 //用戶mapper
 private UserInfoMapper userInfoMapper;
 @Autowired
 //用戶權限mapper
 private PermissionInfoMapper permissionInfoMapper;
 @Override
 public 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 object
        for (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;
  //是否支持處理傳遞的ConfigAttribute
  boolean supports(ConfigAttribute attribute);
  //確認class是否爲AccessDecisionManager
  boolean 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 attributes

        return 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源碼分析:

public void invoke(FilterInvocation fi) throws IOException, ServletException {
	if ((fi.getRequest() != null)
			&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
			&& observeOncePerRequest) {
		fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
	}
	else {
		// first time this request being called, so perform security checking
		if (fi.getRequest() != null && observeOncePerRequest) {
			fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
        //回調其繼承的抽象類AbstractSecurityInterceptor的方法
		InterceptorStatusToken token = super.beforeInvocation(fi);

		try {
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		finally {
			super.finallyInvocation(token);
		}

		super.afterInvocation(token, null);
	}
}
複製代碼

AbstractSecurityInterceptor源碼分析:

protected InterceptorStatusToken beforeInvocation(Object object) {
	....
	//獲取全部訪問權限(url-role)屬性列表(已定義在數據庫或者其餘地方)
	Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
			.getAttributes(object);
	....
	//獲取該用戶訪問信息(包括url,訪問權限)
	Authentication authenticated = authenticateIfRequired();

	// Attempt authorization
	try {
	    //進行受權訪問
		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形式的token
     UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
     username, password);
     // Allow subclasses to set the "details" property
     setDetails(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 implements
	InitializingBean {
	...
	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認證和受權還有不少負責的過程須要深刻了解,因此下次會對認證模塊和受權模塊進行更具體工做流程分析以及案例呈現。最後以上純粹我的結合博客和官方文檔總結,若有錯請指出!

相關文章
相關標籤/搜索