<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    淺析 Spring Security 核心組件

    一顆小胡椒2022-07-27 17:46:24

    前言

    近幾天在網上找了一個 Spring Security 和 JWT 的例子來學習,項目地址是

    https://github.com/szerhusenBC/jwt-spring-security-demo 
    

    還是有發現的,作為該研究 的一篇文章,通過對Spring SecuritySpring Security 的一個優秀的流程進行分析,并沒有參考這篇文章先對 Spring Security 的流程進行分析,并想寫一篇官方文檔和 Spring Security 的核心組件一些大佬寫的Spring Security分析文章,有雷同的地方還請見諒。

    Spring Security的核心類

    Spring Security 的核心類主要包括以下幾個:

    • SecurityContextHolder :存放身份信息的容器
    • Authentication : 身份信息的抽象接口
    • AuthenticationManager : 身份認證器,認證的核心接口
    • UserDetailsS??ervice:一般用于從數據庫中加載身份信息
    • UserDetails : 比較認證,有更詳細的身份信息

    SecurityContextHolder、Securityontext和Authentication

    SecurityContextHolder存儲安全信息(安全上下文)的信息,即用于存儲身份,認證信息等的包含。SecurityContextHolder默認使用 ThreadLocal策略來存儲認證信息,即一種與線程綁定的策略,各個線程執行時都可以獲取該線程中的安全時間(安全上下文),影響線程中的線程如果在請求中的安全時間互不影響。

    因為身份信息是與當前會話的關系,所以我們可以在程序的任何地方使用獲取用戶信息的方法,登錄用戶的姓名的例子如下:

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (principal instanceof UserDetails) {
         String username = ((UserDetails)principal).getUsername();
    } else {
         String username = principal.toString();
    }
    

    getAuthentication()返回方法獲得了認證信息,準確的說是一個 Authentication實例,AuthenticationSpring Security 中的一個重要接口,直接繼承自 Principal 類,應該表示對用戶身份信息的抽象,接口來源如下:

    public interface Authentication extends Principal, Serializable { 
        //權限信息列表,默認是 GrantedAuthority接口的一些實現
        Collection<? extends GrantedAuthority> getAuthorities(); 
        //密碼信息,用戶輸入的密碼字符串,認證后通常會被移除,用于保證安全
        Object getCredentials();
        //細節信息,web應用中通常的接口為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值
        Object getDetails();
        //身份信息,返回UserDetails的實現類
        Object getPrincipal();
        //認證狀態,默認為false,認證成功后為 true
        boolean isAuthenticated();
        //上述身份信息是否經過身份認證 
        void setAuthenticated(boolean var1) throws IllegalArgumentException;
    }
    

    AuthenticationManager、ProviderManager 和 AuthenticationProvider

    AuthenticationManager是身份認證器,認證的核心接口,接口源如下:

    public interface AuthenticationManager {
      /**
       * Attempts to authenticate the passed {@link Authentication} object, returning a
       * fully populated <code>Authentication</code> object (including granted authorities)
       * @param authentication the authentication request object
       *
       * @return a fully authenticated object including credentials
       *
       * @throws AuthenticationException if authentication fails
       */
      Authentication authenticate(Authentication authentication)
          throws AuthenticationException;
    }
    

    該接口只有一個 authenticate()方法,用于信息,如果認證成功,則返回一個完整的身份信息Authentication,在之前提到的Authentication所有屬性都會被填充。

    Spring Security 中,一個自己AuthenticationManager默認 對進行請求ProviderManagerProviderManager將派給給自己 AuthenticationProvider的每個人都需要在一個目標列表中 實現目標的AuthenticationProvider跟蹤服務提供者。驗證結果只有兩種情況:拋出一個異常或者完全填充一個 對象的Authentication所有屬性。ProviderManager

    public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
      //維護一個AuthenticationProvider 列表
      private List<AuthenticationProvider> providers = Collections.emptyList();
      private AuthenticationManager parent;
      //構造器,初始化 AuthenticationProvider 列表
      public ProviderManager(List<AuthenticationProvider> providers) {
        this(providers, null);
      }
      public ProviderManager(List<AuthenticationProvider> providers,
          AuthenticationManager parent) {
        Assert.notNull(providers, "providers list cannot be null");
        this.providers = providers;
        this.parent = parent;
        checkState();
      }
      public Authentication authenticate(Authentication authentication)
          throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
            // AuthenticationProvider 列表中每個Provider依次進行認證
        for (AuthenticationProvider provider : getProviders()) {
          if (!provider.supports(toTest)) {
            continue;
          }
                ...
          try { 
              //調用 AuthenticationProvider 的 authenticate()方法進行認證
            result = provider.authenticate(authentication);
            if (result != null) {
              copyDetails(authentication, result);
              break;
            }
          }
          ...
          catch (AuthenticationException e) {
            lastException = e;
          }
        }
            // 如果 AuthenticationProvider 列表中的Provider都認證失敗,且之前有構造一個 AuthenticationManager 實現類,那么利用AuthenticationManager 實現類 繼續認證
        if (result == null && parent != null) {
          // Allow the parent to try.
          try {
            result = parent.authenticate(authentication);
          }
                ...
          catch (AuthenticationException e) {
            lastException = e;
          }
        }
            //認證成功
        if (result != null) {
          if (eraseCredentialsAfterAuthentication
              && (result instanceof CredentialsContainer)) {
            // Authentication is complete. Remove credentials and other secret data
            // from authentication
            //成功認證后刪除驗證信息
            ((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中的 authenticationManager成功成功失敗成功嘗試,認證即返回失敗,如果所有的Provider都認證,認證失敗返回 ProviderManager無效 ProviderNotFoundException

    是一個接口,接口定義如下:AuthenticationProvider

    public interface AuthenticationProvider {
        //認證方法
      Authentication authenticate(Authentication authentication)
          throws AuthenticationException;
        //該Provider是否支持對應的Authentication
      boolean supports(Class<?> authentication);
    }
    

    在 ProviderManager的 Javadoc 曾提到,

    如果多個 AuthenticationProvider 支持傳遞的 Authentication 對象,則第一個能夠成功驗證 Authentication 對象的人會確定結果,并覆蓋之前支持的 AuthenticationProvider 拋出的任何可能的 AuthenticationException 。成功認證后,不會嘗試后續的 AuthenticationProvider 。如果任何支持 AuthenticationProvider 的身份驗證未成功,則最后拋出的 AuthenticationException 將被重新拋出

    大致意思是:

    如果有多個AuthenticationProvider都支持同一個Authentication對象,那么第一個能夠成功驗證的Provder將填充 其返回結果,那么早期支持的AuthenticationProvider拋出可能的AuthenticationException。成功驗證后,將如果所有驗證都不會在中驗證)的AuthenticationProvider。如果 AuthenticationProvider沒有成功驗證,則將拋出最后的ProviderProvider拋出異常。(AuthenticationProvider可以配置配置類的配置)

    PS

    不同的 AuthenticationProvider認證支持不同的不同的對象,分別對應不同的 Authentication對象,那么當一個 不同的對象AuthenticationProvier進入 ProviderManager的內部時, 它們會在AuthenticationProvider它們中挑選其對的提供者相應的對象進行驗證。

    不同的認證邏輯是不同的,即 使用用戶名和密碼,使用用戶名和密碼,登錄提供了簡單的實現 ,這也是 一個用戶在登錄時使用的方式AuthenticationProvider,如果用戶名和密碼不一樣,那么 它也是一個用戶可以登錄的方式。密碼和 我們一般要在接口中,并把 Spring Security 配置類實現其配置,這樣也用于在使用中進行詳細的認證,然后該接口返回一個,它包含了身份信息,比如從數據庫拿取的信息密碼和AuthenticationProvider的認證核心,即加載的 來列表用戶的密碼是否匹配,用戶詳情和驗證以及其他的密碼(關于 和就是使用的介紹在下面介紹。)。,比如QQ登錄,那么就需要設置的 ,這里就不細說了。AuthenticationProviderDaoAuthenticationProviderUserDetailsServiceGrantedAuthorityUserDetailsServiceDaoAuthenticationProviderUserDetailsUserDetailsUserDetailsServiceUserDetailsAuthenticationProvider

    認證成功后清除驗證信息

    在 ProviderManager 的源中我還發現一點,在認證成功后清除驗證信息,如下:

    if (eraseCredentialsAfterAuthentication
        && (result instanceof CredentialsContainer)) {
      // Authentication is complete. Remove credentials and other secret data
      // from authentication
      //成功認證后刪除驗證信息
      ((CredentialsContainer) result).eraseCredentials();
    }
    

    從安全 3.1,在請求認證成功后 ProviderManager刪除 Authenticationspring 中的認證信息,準確地說,一般刪除是密碼信息,這保證跟密碼的安全。我可以直接執行刪除操作的步驟如下:

    public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
        public void eraseCredentials() {
            super.eraseCredentials();
            //使密碼為null
            this.credentials = null;
        }
    }
    public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
    ...
    public void eraseCredentials() {
        //擦除密碼
        this.eraseSecret(this.getCredentials());
        this.eraseSecret(this.getPrincipal());
        this.eraseSecret(this.details);
    }
    private void eraseSecret(Object secret) {
        if (secret instanceof CredentialsContainer) {
            ((CredentialsContainer)secret).eraseCredentials();
        }
     }
    }
    

    從就可以很舒服地使用私人密碼。

    UserDetailsS??ervice 和 UserDetails

    UserDetailsService簡單說就是加載UserDetails的接口(一般從數據庫),而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的getCredentials()一般與Users中的getPassword()不一樣,是用戶提交的密碼,后面是用戶正確的從數據庫加載的密碼),AuthenticationProvider分別對中進行對比。
    • Authentication 中的 getAuthorities() 形成是由 UserDetails 的 getAuthorities() 傳遞的。
    • Authentication 中的 getUserDetails() 中的 UserDetails 用戶詳細信息經過 AuthenticationProvider認證后填充的。

    認證樣本示例

    下面來看一個官方文檔提供的例子,代碼如下:

    public class SpringSecuriryTestDemo {
        private static AuthenticationManager am = new SampleAuthenticationManager();
        public static void main(String[] args) throws IOException {
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                System.out.println("Please enter your username:");
                String name = in.readLine();
                System.out.println("Please enter your password:");
                String password = in.readLine();
                try {
                    Authentication request = new UsernamePasswordAuthenticationToken(name, password);
                    Authentication result = am.authenticate(request);
                    SecurityContextHolder.getContext().setAuthentication(request);
                    break;
                } catch (AuthenticationException e) {
                    System.out.println("Authentication failed: " + e.getMessage());
                }
            }
            System.out.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication());
        }
        static class SampleAuthenticationManager implements AuthenticationManager {
            static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
            static {
                AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
            }
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (authentication.getName().equals(authentication.getCredentials())) {
                    return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES);
                }
                throw new BadCredentialsException("Bad Credentials");
            }
        }
    }
    

    測試如下:

    Please enter your username:
    pjmike
    Please enter your password:
    123
    Authentication failed: Bad Credentials
    Please enter your username:
    pjmike
    Please enter your password:
    pjmike
    Successfully authenticated. 
    Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230:
    Principal: pjmike; 
    Credentials: [PROTECTED];
    Authenticated: true; Details: null; 
    Granted Authorities: ROLE_USER
    

    上面的例子很不是源頭,只是為了證明的Demo,而且很簡單的驗證過程,但麻雀雖小,五臟俱全,包括Spring Security的核心組件,思想了Spring Security認證的基本原理解讀一下:

    • 用戶名和密碼被封裝到 UsernamePasswordAuthentication的實例中(該類是 Authentication接口的實現)
    • 該 Authentication遞交給 AuthenticationManager進行身份驗證
    • 認證成功后,AuthenticationManager返回一個完整的 Authentication身份實例,該實例包含權限信息、信息、細節信息,但密碼會被刪除
    • 通過調用 的對象SecurityContextHolder.getContext().setAuthentication(…)返回的信息 Authentication

    通過上面一個簡單的例子,我們大致了解了 Spring Security 的基本思想,但是要理清 Spring Security 的認證流程這件事,我們還需要深入了解 Spring Security 的認證流程,深入了解 Spring Security 的認證流程。

    小結

    這篇文章主要介紹了這篇文檔后分析了一些核心組件,參考了官方相關的一些核心組件,對有一個基本組件,Spring 核心才能解釋 Spring Security 的一些詳細的分析認證過程。

    用戶接口
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    目前,多數項目會有多數據源的要求,或者是主從部署的要求,所以我們還需要引入mybatis-plus關于多數據源的依賴:。#設置默認的數據源或者數據源組,默認值即為master. true未匹配到指定數據源時拋異常,false使用默認數據源。表名注解,用于標識實體類對應的表。其說明如下,關于這些書寫,常規情況基本很少用到,不做多余解釋了:@Documented
    OWT滲透WiFi網絡
    2021-10-20 21:51:30
    一款功能強大的攻擊性WiFi滲透測試套件
    SharpStrike是一款基于C#開發的后滲透工具,該工具可以使用CIM或WMI來查詢遠程系統。除此之外,該工具還可以使用研究人員提供的憑證信息或使用當前的用戶會話。 注意:SharpStrike中的某些命令將使用PowerShell結合WMI以實現其功能。
    關于CloudPulseCloudPulse是一款針對AWS云環境的SSL證書搜索與分析引擎,廣大研究人員可以使用該工具簡化并增強針對SSL證書數據的檢索和分析過程。在網絡偵查階段,我們往往需要收集與目標相關的信息,并為目標創建一個專用文檔,以輔助我們識別目標組織可用的滲透路徑。CloudPulse能夠從Trickest Cloud中的AWS EC2主機中獲取大量的SSL證書,并以此來簡化我們針對
    一次簡單的滲透測試記錄
    0x01 目標某平臺系統0x02 流程0x03 測試拿到站點后先做信息收集,掃描目錄看看有無敏感信息寥寥無幾,沒有任何信息,啟動burpsuite打開網站走一遍流程。在創建目標處存在圖片上傳接口,上傳shell試試。
    Pentaho Business Analytics CVE-2021-31599遠程命令執行等系列漏洞分析。
    據外媒,全球知名航空通訊公司SITA表示其航空客運系統疑似遭遇數據泄露事件,該公司在2月24日的一份聲明中表示,存儲在其美國服務器上的某些乘客數據已被泄露。并聯系了受影響的航空公司。
    ?如何看待阿里云等大廠平臺相繼發生崩潰故障?
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类