最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Spring Security認證流程

 更新時間:2020年02月06日 08:34:07   作者:donespeak  
這篇文章主要介紹了Spring Security認證流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

Spring Seuciry相關的內容看了實在是太多了,但總覺得還是理解地不夠鞏固,還是需要靠知識輸出做鞏固。

相關版本:

java: jdk 8 
spring-boot: 2.1.6.RELEASE 

過濾器鏈和認證過程

一個認證過程,其實就是過濾器鏈上的一個綠色矩形Filter所要執(zhí)行的過程。

基本的認證過程有三步驟:

  1. Filter攔截請求,生成一個未認證的Authentication,交由AuthenticationManager進行認證;
  2. AuthenticationManager的默認實現ProviderManager會通過AuthenticationProviderAuthentication進行認證,其本身不做認證處理;
  3. 如果認證通過,則創(chuàng)建一個認證通過的Authentication返回;否則拋出異常,以表示認證不通過。

要理解這個過程,可以從類UsernamePasswordAuthenticationFilter,ProviderManagerDaoAuthenticationProviderInMemoryUserDetailsManagerUserDetailsService實現類,由UserDetailsServiceAutoConfiguration默認配置提供)進行了解。只要創(chuàng)建一個含有spring-boot-starter-security的springboot項目,在適當地打上斷點接口看到這個流程。

用認證部門進行講解

)

請求到前臺之后,負責該請求的前臺會將請求的內容封裝為一個Authentication對象交給認證管理部門,認證管理部門僅管理認證部門,不做具體的認證操作,具體的操作由與該前臺相關的認證部門進行處理。當然,每個認證部門需要判斷Authentication是否為該部門負責,是則由該部門負責處理,否則交給下一個部門處理。認證部門認證成功之后會創(chuàng)建一個認證通過的Authentication返回。否則要么拋出異常表示認證不通過,要么交給下一個部門處理。

如果需要新增認證類型,只要增加相應的前臺(Filter)和與該前臺(Filter)想對應的認證部門(AuthenticationProvider)就即可,當然也可以增加一個與已有前臺對應的認證部門。認證部門會通過前臺生成的Authentication來判斷該認證是否由該部門負責,因而也許提供一個兩者相互認同的Authentication.

認證部門需要人員資料時,則可以從人員資料部門獲取。不同的系統有不同的人員資料部門,需要我們提供該人員資料部門,否則將拿到空白檔案。當然,人員資料部門不一定是唯一的,認證部門可以有自己的專屬資料部門。

上圖還可以有如下的畫法:

這個畫法可能會和FilterChain更加符合。每一個前臺其實就是FilterChain中的一個,客戶拿著請求逐個前臺請求認證,找到正確的前臺之后進行認證判斷。

前臺(Filter)

這里的前臺Filter僅僅指實現認證的Filter,Spring Security Filter Chain中處理這些Filter還有其他的Filter,比如CsrfFilter。如果非要給角色給他們,那么就當他們是保安人員吧。

Spring Security為我們提供了3個已經實現的Filter。UsernamePasswordAuthenticationFilter,BasicAuthenticationFilterRememberMeAuthenticationFilter。如果不做任何個性化的配置,UsernamePasswordAuthenticationFilterBasicAuthenticationFilter會在默認的過濾器鏈中。這兩種認證方式也就是默認的認證方式。

UsernamePasswordAuthenticationFilter僅僅會對/login路徑生效,也就是說UsernamePasswordAuthenticationFilter負責發(fā)布認證,發(fā)布認證的接口為/login。

public class UsernamePasswordAuthenticationFilter extends
    AbstractAuthenticationProcessingFilter {
  ...
  public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
  }
  ...
}

UsernamePasswordAuthenticationFilter為抽象類AbstractAuthenticationProcessingFilter的一個實現,而BasicAuthenticationFilter為抽象類BasicAuthenticationFilter的一個實現。這四個類的源碼提供了不錯的前臺(Filter)實現思路。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 提供了認證前后需要做的事情,其子類只需要提供實現完成認證的抽象方法attemptAuthentication(HttpServletRequest, HttpServletResponse)即可。使用AbstractAuthenticationProcessingFilter時,需要提供一個攔截路徑(使用AntPathMatcher進行匹配)來攔截對應的特定的路徑。

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter作為實際的前臺,會將客戶端提交的username和password封裝成一個UsernamePasswordAuthenticationToken交給認證管理部門(AuthenticationManager)進行認證。如此,她的任務就完成了。

BasicAuthenticationFilter
前臺(Filter)只會處理含有Authorization的Header,且小寫化后的值以basic開頭的請求,否則該前臺(Filter)不負責處理。該Filter會從header中獲取Base64編碼之后的username和password,創(chuàng)建UsernamePasswordAuthenticationToken提供給認證管理部門(AuthenticationMananager)進行認證。

認證資料(Authentication)

前臺接到請求之后,會從請求中獲取所需的信息,創(chuàng)建自家認證部門(AuthenticationProvider)所認識的認證資料(Authentication),認證部門(AuthenticationProvider)則主要是通過認證資料(Authentication)的類型判斷是否由該部門處理。

public interface Authentication extends Principal, Serializable {
  
  // 該principal具有的權限。AuthorityUtils工具類提供了一些方便的方法。
  Collection<? extends GrantedAuthority> getAuthorities();
  // 證明Principal的身份的證書,比如密碼。
  Object getCredentials();
  // authentication request的附加信息,比如ip。
  Object getDetails();
  // 當事人。在username+password模式中為username,在有userDetails之后可以為userDetails。
  Object getPrincipal();
  // 是否已經通過認證。
  boolean isAuthenticated();
  // 設置通過認證。
  void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication被認證之后,會保存到一個thread-local的SecurityContext中。

// 設置
SecurityContextHolder.getContext().setAuthentication(anAuthentication);
// 獲取
Authentication existingAuth = SecurityContextHolder.getContext()
        .getAuthentication();

在寫前臺Filter的時候,可以先檢查SecurityContextHolder.getContext()中是否已經存在通過認證的Authentication了,如果存在,則可以直接跳過該Filter。已經通過驗證的Authentication建議設置為一個不可修改的實例。

目前從Authentication的類圖中看到的實現類,均為Authentication的抽象子類AbstractAuthenticationToken的實現類。實現類有好幾個,與前面的講到的Filter相關的有UsernamePasswordAuthenticationTokenRememberMeAuthenticationToken

AbstractAuthenticationTokenCredentialsContainerAuthentication的子類。實現了一些簡單的方法,但主要的方法還需要實現。該類的getName()方法的實現可以看到常用的principal類為UserDetailsAuthenticationPrincipalPrincial。如果有需要將對象設置為principal,可以考慮繼承這三個類中的一個。

public String getName() {
  if (this.getPrincipal() instanceof UserDetails) {
    return ((UserDetails) this.getPrincipal()).getUsername();
  }
  if (this.getPrincipal() instanceof AuthenticatedPrincipal) {
    return ((AuthenticatedPrincipal) this.getPrincipal()).getName();
  }
  if (this.getPrincipal() instanceof Principal) {
    return ((Principal) this.getPrincipal()).getName();
  }

  return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();
}

認證管理部門(AuthenticationManager)

AuthenticationManager是一個接口,認證Authentication,如果認證通過之后,返回的Authentication應該帶上該principal所具有的GrantedAuthority。

public interface AuthenticationManager {
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
}

該接口的注釋中說明,必須按照如下的異常順序進行檢查和拋出:

  • DisabledException:賬號不可用
  • LockedException:賬號被鎖
  • BadCredentialsException:證書不正確

Spring Security提供一個默認的實現ProviderManager。認證管理部門(ProviderManager)僅執(zhí)行管理職能,具體的認證職能由認證部門(AuthenticationProvider)執(zhí)行。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
    InitializingBean {
  ...

  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;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();

    for (AuthenticationProvider provider : getProviders()) {
      // #1, 檢查是否由該認證部門進行認證`AuthenticationProvider`
      if (!provider.supports(toTest)) {
        continue;
      }

      if (debug) {
        logger.debug("Authentication attempt using "
            + provider.getClass().getName());
      }

      try {
        // #2, 認證部門進行認證
        result = provider.authenticate(authentication);

        if (result != null) {
          copyDetails(authentication, result);
          // #3,認證通過則不再進行下一個認證部門的認證,否則拋出的異常被捕獲,執(zhí)行下一個認證部門(AuthenticationProvider)
          break;
        }
      }
      catch (AccountStatusException e) {
        prepareException(e, authentication);
        // SEC-546: Avoid polling additional providers if auth failure is due to
        // invalid account status
        throw e;
      }
      catch (InternalAuthenticationServiceException e) {
        prepareException(e, authentication);
        throw e;
      }
      catch (AuthenticationException e) {
        lastException = e;
      }
    }

    if (result == null && parent != null) {
      // Allow the parent to try.
      try {
        result = parentResult = parent.authenticate(authentication);
      }
      catch (ProviderNotFoundException e) {
        // ignore as we will throw below if no other exception occurred prior to
        // calling parent and the parent
        // may throw ProviderNotFound even though a provider in the child already
        // handled the request
      }
      catch (AuthenticationException e) {
        lastException = parentException = e;
      }
    }
    // #4, 如果認證通過,執(zhí)行認證通過之后的操作
    if (result != null) {
      if (eraseCredentialsAfterAuthentication
          && (result instanceof CredentialsContainer)) {
        // Authentication is complete. Remove credentials and other secret data
        // from authentication
        ((CredentialsContainer) result).eraseCredentials();
      }

      // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
      // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
      if (parentResult == null) {
        eventPublisher.publishAuthenticationSuccess(result);
      }
      return result;
    }

    // Parent was null, or didn't authenticate (or throw an exception).
    // #5,如果認證不通過,必然有拋出異常,否則表示沒有配置相應的認證部門(AuthenticationProvider)
    if (lastException == null) {
      lastException = new ProviderNotFoundException(messages.getMessage(
          "ProviderManager.providerNotFound",
          new Object[] { toTest.getName() },
          "No AuthenticationProvider found for {0}"));
    }

    // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
    // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
    if (parentException == null) {
      prepareException(lastException, authentication);
    }

    throw lastException;
  }
  ...
}

遍歷所有的認證部門(AuthenticationProvider),找到支持的認證部門進行認證認證部門進行認證認證通過則不再進行下一個認證部門的認證,否則拋出的異常被捕獲,執(zhí)行下一個認證部門(AuthenticationProvider)如果認證通過,執(zhí)行認證通過之后的操作如果認證不通過,必然有拋出異常,否則表示沒有配置相應的認證部門(AuthenticationProvider)

當使用到Spring Security OAuth2的時候,會看到另一個實現OAuth2AuthenticationManager。

認證部門(AuthenticationProvider)

認證部門(AuthenticationProvider)負責實際的認證工作,與認證管理部門(ProvderManager)協同工作。也許其他的認證管理部門(AuthenticationManager)并不需要認證部門(AuthenticationProvider)的協作。

public interface AuthenticationProvider {
  // 進行認證
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
  // 是否由該AuthenticationProvider進行認證
  boolean supports(Class<?> authentication);
}

該接口有很多的實現類,其中包含了RememberMeAuthenticationProvider(直接AuthenticationProvider)和DaoAuthenticationProvider(通過AbastractUserDetailsAuthenticationProvider簡介繼承)。這里重點講講AbastractUserDetailsAuthenticationProviderDaoAuthenticationProvider。

AbastractUserDetailsAuthenticationProvider

顧名思義,AbastractUserDetailsAuthenticationProvider是對UserDetails支持的Provider,其他的Provider,如RememberMeAuthenticationProvider就不需要用到UserDetails。該抽象類有兩個抽象方法需要實現類完成:

// 獲取 UserDetails
protected abstract UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

retrieveUser()方法為校驗提供UserDetails。先看下UserDetails:

public interface UserDetails extends Serializable {
  
  Collection<? extends GrantedAuthority> getAuthorities();

  String getPassword();
  
  String getUsername();
  // 賬號是否過期
  boolean isAccountNonExpired();
  // 賬號是否被鎖
  boolean isAccountNonLocked();
  // 證書(password)是否過期
  boolean isCredentialsNonExpired();
  // 賬號是否可用
  boolean isEnabled();
}

AbastractUserDetailsAuthenticationProvider#authentication(Authentication)分為三步驗證:

  • preAuthenticationChecks.check(user);
  • additionalAuthenticationChecks(user,
  • (UsernamePasswordAuthenticationToken) authentication);
  • postAuthenticationChecks.check(user);

preAuthenticationChecks的默認實現為DefaultPreAuthenticationChecks,負責完成校驗:

  1. UserDetails#isAccountNonLocked()
  2. UserDetails#isEnabled()
  3. UserDetails#isAccountNonExpired()

postAuthenticationChecks的默認實現為DefaultPostAuthenticationChecks,負責完成校驗:

UserDetails#user.isCredentialsNonExpired()

additionalAuthenticationChecks需要由實現類完成。

校驗成功之后,AbstractUserDetailsAuthenticationProvider會創(chuàng)建并返回一個通過認證的Authentication。

protected Authentication createSuccessAuthentication(Object principal,
    Authentication authentication, UserDetails user) {
  // Ensure we return the original credentials the user supplied,
  // so subsequent attempts are successful even with encoded passwords.
  // Also ensure we return the original getDetails(), so that future
  // authentication events after cache expiry contain the details
  UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
      principal, authentication.getCredentials(),
      authoritiesMapper.mapAuthorities(user.getAuthorities()));
  result.setDetails(authentication.getDetails());

  return result;
}

DaoAuthenticationProvider

如下為DaoAuthenticationProviderAbstractUserDetailsAuthenticationProvider抽象方法的實現。

// 檢查密碼是否正確
protected void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  if (authentication.getCredentials() == null) {
    logger.debug("Authentication failed: no credentials provided");

    throw new BadCredentialsException(messages.getMessage(
        "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }

  String presentedPassword = authentication.getCredentials().toString();

  if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    logger.debug("Authentication failed: password does not match stored value");

    throw new BadCredentialsException(messages.getMessage(
        "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }
}
// 通過資料室(UserDetailsService)獲取UserDetails對象
protected final UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  prepareTimingAttackProtection();
  try {
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    if (loadedUser == null) {
      throw new InternalAuthenticationServiceException(
          "UserDetailsService returned null, which is an interface contract violation");
    }
    return loadedUser;
  }
  ...
}

在以上的代碼中,需要提供UserDetailsServicePasswordEncoder實例。只要實例化這兩個類,并放入到Spring容器中即可。

資料部門(UserDetailsService)

UserDetailsService接口提供認證過程所需的UserDetails的類,如DaoAuthenticationProvider需要一個UserDetailsService實例。

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

Spring Security提供了兩個UserDetailsService的實現:InMemoryUserDetailsManagerJdbcUserDetailsManager。InMemoryUserDetailsManager為默認配置,從UserDetailsServiceAutoConfiguration的配置中可以看出。當然也不容易理解,基于數據庫的實現需要增加數據庫的配置,不適合做默認實現。這兩個類均為UserDetailsManager的實現類,UserDetailsManager定義了UserDetails的CRUD操作。InMemoryUserDetailsManager使用Map<String, MutableUserDetails>做存儲。

public interface UserDetailsManager extends UserDetailsService {
  void createUser(UserDetails user);

  void updateUser(UserDetails user);

  void deleteUser(String username);

  void changePassword(String oldPassword, String newPassword);

  boolean userExists(String username);
}

如果我們需要增加一個UserDetailsService,可以考慮實現UserDetailsService或者UserDetailsManager

增加一個認證流程

到這里,我們已經知道Spring Security的流程了。從上面的內容可以知道,如要增加一個新的認證方式,只要增加一個[前臺(Filter) + 認證部門(AuthenticationProvider) + 資料室(UserDetailsService)]組合即可。事實上,資料室(UserDetailsService)不是必須的,可根據認證部門(AuthenticationProvider)需要實現。

我會在另一篇文章中以手機號碼+驗證碼登錄為例進行講解。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Spring Security系列教程之會話管理處理會話過期問題

    Spring Security系列教程之會話管理處理會話過期問題

    會話過期,是指當用戶登錄網站后,較長一段時間沒有與服務器進行交互,將會導致服務器上的用戶會話數據(即session)被銷毀。這篇文章主要介紹了Spring Security系列教程之會話管理處理會話過期問題,需要的朋友可以參考下
    2021-10-10
  • Java使用Tessdata做OCR圖片文字識別的詳細思路

    Java使用Tessdata做OCR圖片文字識別的詳細思路

    這篇文章主要介紹了Java使用Tessdata做OCR圖片文字識別的詳細思路,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • Spring AbstractRoutingDatasource 動態(tài)數據源的實例講解

    Spring AbstractRoutingDatasource 動態(tài)數據源的實例講解

    本文介紹如何使用 Spring AbstractRoutingDatasource 基于上下文動態(tài)切換數據源,因此我們會讓查找數據源邏輯獨立于數據訪問之外
    2021-07-07
  • Java中Stream流Map分組方式詳細匯總

    Java中Stream流Map分組方式詳細匯總

    Stream將要處理的元素集合看作一種流,在流的過程中借助Stream?API對流中的元素進行操作,比如篩選、排序、聚合等,下面這篇文章主要給大家介紹了關于Java中Stream流Map分組方式的相關資料,需要的朋友可以參考下
    2024-01-01
  • Java?遠程調用失敗重試的操作方法

    Java?遠程調用失敗重試的操作方法

    這篇文章主要介紹了Java?遠程調用失敗重試的操作方法,今天給大家介紹了一下?Spring??的?@Retryable?注解使用,并通過幾個 demo 來帶大家編寫了自己重試攔截器以及回滾方法,需要的朋友可以參考下
    2022-09-09
  • java簡單解析xls文件的方法示例【讀取和寫入】

    java簡單解析xls文件的方法示例【讀取和寫入】

    這篇文章主要介紹了java簡單解析xls文件的方法,結合實例形式分析了java針對xls文件的讀取和寫入相關操作技巧與注意事項,需要的朋友可以參考下
    2017-06-06
  • springBoot 插件工具熱部署 Devtools的步驟詳解

    springBoot 插件工具熱部署 Devtools的步驟詳解

    這篇文章主要介紹了springBoot 插件工具 熱部署 Devtools,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • SpringCache的基本使用方法

    SpringCache的基本使用方法

    Spring?Cache利用了AOP,實現了基于注解的緩存功能,并且進行了合理的抽象,業(yè)務代碼不用關心底層是使用了什么緩存框架,只需要簡單地加一個注解,就能實現緩存功能了,本文介紹SpringCache的基本使用方法,感興趣的朋友一起看看吧
    2024-01-01
  • IP查詢系統的異步回調案例

    IP查詢系統的異步回調案例

    本文主要分享了IP查詢系統的異步回調案例,具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • Java的動態(tài)代理模式之JDK代理詳解

    Java的動態(tài)代理模式之JDK代理詳解

    這篇文章主要介紹了Java的動態(tài)代理模式之JDK代理詳解,代理對象,不需要實現接口,但是目標對象要實現接口,否則不能用動態(tài)代理,JDK?實現代理只需要使用?newProxyInstance?方法,但是該方法需要接收三個參數,需要的朋友可以參考下
    2023-11-11

最新評論

青浦区| 靖宇县| 滨州市| 类乌齐县| 永春县| 柞水县| 满洲里市| 阿坝| 利津县| 高淳县| 天全县| 黄大仙区| 亚东县| 石楼县| 莫力| 京山县| 太原市| 和田县| 抚州市| 永平县| 杭锦后旗| 集安市| 司法| 平潭县| 茶陵县| 民勤县| 苗栗市| 新巴尔虎右旗| 新化县| 威信县| 天祝| 银川市| 泊头市| 晋中市| 武胜县| 奇台县| 通江县| 富川| 沂南县| 淄博市| 卓尼县|