SpringSecurity半成品筆記

參考以下文章整理的筆記:web

#1 Authentication認證信息spring

認證前:用來表示用戶輸入的認證信息的對象安全

認證後:包含用戶詳細信息以及權限信息的對象cookie

接口定義以下:session

public interface Authentication extends Principal, Serializable {

	//獲取用戶權限
    Collection<? extends GrantedAuthority> getAuthorities();

	//獲取密碼信息
    Object getCredentials();

    Object getDetails();

	//獲取用戶惟一標示,如用戶名
    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

接口實現以下:app

Authentication接口實現

#2 SecurityContextHolder用來保存SecurityContext信息的jsp

SecurityContextHolder默認使用ThreadLocalSecurityContextHolderStrategy策略來保存。ide

ThreadLocalSecurityContextHolderStrategy策略實現就是使用ThreadLocal來保存post

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
}

SecurityContext又是用來保存上述Authentication信息,接口定義以下:ui

public interface SecurityContext extends Serializable {

    Authentication getAuthentication();

    void setAuthentication(Authentication authentication);
}

#3 AuthenticationManager對用戶輸入信息進行認證

接口定義以下:

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

接口實現以下:

AuthenticationManager接口定義

默認實現ProviderManager,內部包含了一個List類型的AuthenticationProvider providers,也就是說ProviderManager委託內部的AuthenticationProvider來實現認證的

使用以下標籤默認就是建立ProviderManager:

<security:authentication-manager alias="authenticationManager">
  	<security:authentication-provider  user-service-ref="userDetailsService"/>
	<security:authentication-provider  ref="xxxxx"/>
</security:authentication-manager>

#4 AuthenticationProvider對用戶輸入信息進行認證

接口的定義以下:

public interface AuthenticationProvider {

    Authentication authenticate(Authentication authentication)throws AuthenticationException;

    boolean supports(Class<?> authentication);
}

接口實現以下:

AuthenticationProvider接口實現

默認實現DaoAuthenticationProvider:

內部含有一個UserDetailsService userDetailsService對象,用來加載用戶信息包含權限信息

  • 1 使用UserDetailsService userDetailsService來加載用戶信息到UserDetails對象中

    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

    所以在UserDetailsService上述加載過程實現中,咱們就能夠根據用戶名來判斷用戶是否存在。

    UserDetails接口實現,默認就是org.springframework.security.core.userdetails.User,因此咱們通常須要繼承該對象

  • 2 檢查上述用戶信息是否過時、被鎖定等等,分紅preAuthenticationChecks,檢查密碼是否正確和postAuthenticationChecks

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                logger.debug("User account is locked");
    
                throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
                        "User account is locked"), user);
            }
    
            if (!user.isEnabled()) {
                logger.debug("User account is disabled");
    
                throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
                        "User is disabled"), user);
            }
    
            if (!user.isAccountNonExpired()) {
                logger.debug("User account is expired");
    
                throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
                        "User account has expired"), user);
            }
        }
    }
    
    protected void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        Object salt = null;
    
        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }
    
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");
    
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    
        String presentedPassword = authentication.getCredentials().toString();
    
        if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
            logger.debug("Authentication failed: password does not match stored value");
    
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

    其中上述密碼檢查,接口是PasswordEncoder,默認是PlaintextPasswordEncoder,沒有加密過的

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                logger.debug("User account credentials have expired");
    
                throw new CredentialsExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
                        "User credentials have expired"), user);
            }
        }
    }
  • 3 一旦認證經過,構建一個UsernamePasswordAuthenticationToken對象,使用上述UserDetails信息、用戶輸入的密碼信息

    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;
    }

#5 認證過程

  • 一、用戶使用用戶名和密碼進行登陸。
  • 二、Spring Security將獲取到的用戶名和密碼封裝成一個實現了Authentication接口的UsernamePasswordAuthenticationToken。
  • 三、將上述產生的token對象傳遞給AuthenticationManager進行登陸認證。
  • 四、AuthenticationManager認證成功後將會返回一個封裝了用戶權限等信息的Authentication對象。
  • 五、經過調用SecurityContextHolder.getContext().setAuthentication(...)將AuthenticationManager返回的Authentication對象賦予給當前的SecurityContext

ExceptionTranslationFilter是用來處理來自AbstractSecurityInterceptor拋出的AuthenticationException和AccessDeniedException的

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    try {
        chain.doFilter(request, response);

        logger.debug("Chain processed normally");
    }
    catch (IOException ex) {
        throw ex;
    }
    catch (Exception ex) {
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
        RuntimeException ase = (AuthenticationException)
                throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);

        if (ase == null) {
            ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        }

        if (ase != null) {
            handleSpringSecurityException(request, response, chain, ase);
        } else {
            // Rethrow ServletExceptions and RuntimeExceptions as-is
            if (ex instanceof ServletException) {
                throw (ServletException) ex;
            }
            else if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }

            // Wrap other Exceptions. This shouldn't actually happen
            // as we've already covered all the possibilities for doFilter
            throw new RuntimeException(ex);
        }
    }
}

AbstractSecurityInterceptor是Spring Security用於攔截請求進行權限鑑定的,其擁有兩個具體的子類,攔截方法調用的MethodSecurityInterceptor和攔截URL請求的FilterSecurityInterceptor。

當ExceptionTranslationFilter捕獲到的是AuthenticationException時將調用AuthenticationEntryPoint引導用戶進行登陸;

若是捕獲的是AccessDeniedException,可是用戶尚未經過認證,則調用AuthenticationEntryPoint引導用戶進行登陸認證,不然將返回一個表示不存在對應權限的403錯誤碼

  • 1 AbstractSecurityInterceptor對用戶訪問資源進行檢查,若是未認證或者沒權限則拋出異常

  • 2 ExceptionTranslationFilter這個Filter捕獲上述異常信息,引導重定向到登錄界面

  • 3 用戶輸入用戶名和密碼進行認證登錄

  • 4 Spring Security將獲取到的用戶名和密碼封裝成一個實現了Authentication接口的UsernamePasswordAuthenticationToken

  • 5 將上述產生的token對象傳遞給AuthenticationManager進行登陸認證。

  • 6 AuthenticationManager認證成功後將會返回一個封裝了用戶權限等信息的Authentication對象

  • 7 將上述Authentication對象封裝到SecurityContext中

  • 8 SecurityContextPersistentFilter將上述SecurityContext保存到對應的HttpSession屬性中,key爲:SPRING_SECURITY_CONTEXT

    SecurityContextPersistentFilter在Filter執行前,從HttpSession中先嚐試獲取保存的SecurityContext對象信息,若是沒有則建立一個。若是HttpSession沒有的話,看SecurityContextPersistentFilter的forceEagerSessionCreation屬性,若是強制產生,則在此處調用request的getSession方法產生HttpSession

    Filter執行後,會將上述SecurityContext對象信息保存到HttpSession的屬性中

    所以,一樣用戶再次訪問的時候,就不須要進行登陸認證了,只須要從HttpSession中就能取出對應的認證信息SecurityContext了

    也就是說,用戶在某一次請求的過程當中,SecurityContext是保存在ThreadLocal中的,可是請求結束後,就會被清除了。同時,SecurityContext也被保存在HttpSession中,這樣的話,同一個用戶不一樣請求即便是在不一樣的線程上,也都能根據session來獲取到SecurityContext信息。

    SecurityContextPersistentFilter執行邏輯

#6 Filter鏈

Spring Security已經定義了一些Filter,無論實際應用中你用到了哪些,它們應當保持以下順序。

  • (1)ChannelProcessingFilter,若是你訪問的channel錯了,那首先就會在channel之間進行跳轉,如http變爲https。
  • (2)SecurityContextPersistenceFilter,這樣的話在一開始進行request的時候就能夠在SecurityContextHolder中創建一個SecurityContext,而後在請求結束的時候,任何對SecurityContext的改變均可以被copy到HttpSession。
  • (3)ConcurrentSessionFilter,由於它須要使用SecurityContextHolder的功能,並且更新對應session的最後更新時間,以及經過SessionRegistry獲取當前的SessionInformation以檢查當前的session是否已通過期,過時則會調用LogoutHandler。
  • (4)認證處理機制,如UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等,以致於SecurityContextHolder能夠被更新爲包含一個有效的Authentication請求。
  • (5)SecurityContextHolderAwareRequestFilter,它將會把HttpServletRequest封裝成一個繼承自HttpServletRequestWrapper的SecurityContextHolderAwareRequestWrapper,同時使用SecurityContext實現了HttpServletRequest中與安全相關的方法。
  • (6)JaasApiIntegrationFilter,若是SecurityContextHolder中擁有的Authentication是一個JaasAuthenticationToken,那麼該Filter將使用包含在JaasAuthenticationToken中的Subject繼續執行FilterChain。
  • (7)RememberMeAuthenticationFilter,若是以前的認證處理機制沒有更新SecurityContextHolder,而且用戶請求包含了一個Remember-Me對應的cookie,那麼一個對應的Authentication將會設給SecurityContextHolder。
  • (8)AnonymousAuthenticationFilter,若是以前的認證機制都沒有更新SecurityContextHolder擁有的Authentication,那麼一個AnonymousAuthenticationToken將會設給SecurityContextHolder。
  • (9)ExceptionTransactionFilter,用於處理在FilterChain範圍內拋出的AccessDeniedException和AuthenticationException,並把它們轉換爲對應的Http錯誤碼返回或者對應的頁面。
  • (10)FilterSecurityInterceptor,保護Web URI,而且在訪問被拒絕時拋出異常

在web.xml中以下配置:

<filter>
      <filter-name>springSecurityFilterChain</filter-name>
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>

DelegatingFilterProxy實際上是委託給Spring容器中的Filter來執行,如何指定哪個呢?採用DelegatingFilterProxy自身的targetBeanName這個參數

上述就是默認取值spring容器中的這個springSecurityFilterChain Filter,採用的是FilterChainProxy類

當咱們使用基於Spring Security的NameSpace進行配置時,系統會自動爲咱們註冊一個名爲springSecurityFilterChain類型爲FilterChainProxy的bean。該FilterChainProxy含有一個List<SecurityFilterChain> filterChains屬性,咱們以下配置

Spring security容許咱們在配置文件中配置多個http元素,以針對不一樣形式的URL使用不一樣的安全控制。Spring Security將會爲每個http元素建立對應的FilterChain,同時按照它們的聲明順序加入到FilterChainProxy。因此當咱們同時定義多個http元素時要確保將更具備特性的URL配置在前。

<security:http pattern="/login*.jsp*" security="none"/>
   <!-- http元素的pattern屬性指定當前的http對應的FilterChain將匹配哪些URL,如未指定將匹配全部的請求 -->
   <security:http pattern="/admin/**">
      <security:intercept-url pattern="/**" access="ROLE_ADMIN"/>
   </security:http>
   <security:http>
      <security:intercept-url pattern="/**" access="ROLE_USER"/>
   </security:http>

每個security:http都將對應一個SecurityFilterChain。每個SecurityFilterChain中都包含各自的Filter

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);

    List<Filter> filters = getFilters(fwRequest);

    if (filters == null || filters.size() == 0) {
        if (logger.isDebugEnabled()) {
            logger.debug(UrlUtils.buildRequestUrl(fwRequest) +
                    (filters == null ? " has no matching filters" : " has an empty filter list"));
        }

        fwRequest.reset();

        chain.doFilter(fwRequest, fwResponse);

        return;
    }

    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    vfc.doFilter(fwRequest, fwResponse);
}
  • 1 獲取該request訪問的url獲取匹配的security:http,即獲取匹配的SecurityFilterChain,而後返回該SecurityFilterChain的全部Filter
  • 2 上述Filter構建成一個FilterChain鏈
  • 3 執行上述FilterChain鏈

默認註冊了以下Filter

  • SecurityContextPersistenceFilter

    在一開始進行request的時候就能夠在SecurityContextHolder中創建一個SecurityContext,而後在請求結束的時候,任何對SecurityContext的改變均可以被copy到HttpSession

  • LogoutFilter

    檢查是不是登出地址

  • UsernamePasswordAuthenticationFilter

    UsernamePasswordAuthenticationFilter會檢查請求地址是否是配置的登錄地址,若是是則進行認證檢查,從request中獲取username和password構建成UsernamePasswordAuthenticationToken,而後使用AuthenticationManager authenticationManager來進行認證過程,認證成功以後重定向到target地址

  • BasicAuthenticationFilter

  • RequestCacheAwareFilter

    session中還能夠保存着key爲SPRING_SECURITY_SAVED_REQUEST的類型爲DefaultSavedRequest的request信息,每次請求都要從request中獲取session,若是沒有則不會建立session,若是有session,則從session中獲取DefaultSavedRequest的request信息,若是沒有保存的話,則仍然爲null,默認狀況下,這個filter不起做用,由於沒有向session中保存上述信息

  • SecurityContextHolderAwareRequestFilter

  • AnonymousAuthenticationFilter

    若是當前用戶的SecurityContext中的認證信息爲null的話,建立一個默認的角色

  • SessionManagementFilter

  • ExceptionTranslationFilter

    捕獲以後程序執行中的AuthenticationException和AccessDeniedException。若是用戶沒有認證信息,則引導用戶重定向到登錄界面,若是已經有登錄信息,則認爲是權限不足,重定向到權限不足界面

  • FilterSecurityInterceptor

    保護每個受訪問的uri,一旦用戶權限不足,則拋出AccessDeniedException異常

咱們能夠看到整個過程與session打交道的時候僅僅是在SecurityContextPersistenceFilter中,請求完成以後,會將SecurityContext信息保存到session中而已。供下次使用的時候,直接從session中獲取SecurityContext信息,此時的SecurityContext信息中含有用戶的認證信息,所以訪問一個資源的話,上述filter都不會攔截,在FilterSecurityInterceptor中也會被正常經過。

一旦session失效,不能從session中獲取SecurityContext信息了,就會在FilterSecurityInterceptor拋出AccessDeniedException異常,而後被ExceptionTranslationFilter捕獲,引導用戶重定向到登錄界面

相關文章
相關標籤/搜索