中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

SpringSecurity認(rèn)證流程的示例分析

這篇文章主要為大家展示了“Spring Security認(rèn)證流程的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Spring Security認(rèn)證流程的示例分析”這篇文章吧。

創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),天全企業(yè)網(wǎng)站建設(shè),天全品牌網(wǎng)站建設(shè),網(wǎng)站定制,天全網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,天全網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

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

相關(guān)版本:

java: jdk 8 
spring-boot: 2.1.6.RELEASE

過濾器鏈和認(rèn)證過程

Spring Security認(rèn)證流程的示例分析

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

基本的認(rèn)證過程有三步驟:

  1. Filter攔截請求,生成一個未認(rèn)證的Authentication,交由AuthenticationManager進行認(rèn)證;

  2. AuthenticationManager的默認(rèn)實現(xiàn)ProviderManager會通過AuthenticationProviderAuthentication進行認(rèn)證,其本身不做認(rèn)證處理;

  3. 如果認(rèn)證通過,則創(chuàng)建一個認(rèn)證通過的Authentication返回;否則拋出異常,以表示認(rèn)證不通過。

要理解這個過程,可以從類UsernamePasswordAuthenticationFilter,ProviderManager,DaoAuthenticationProviderInMemoryUserDetailsManagerUserDetailsService實現(xiàn)類,由UserDetailsServiceAutoConfiguration默認(rèn)配置提供)進行了解。只要創(chuàng)建一個含有spring-boot-starter-security的springboot項目,在適當(dāng)?shù)卮蛏蠑帱c接口看到這個流程。

用認(rèn)證部門進行講解

Spring Security認(rèn)證流程的示例分析)

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

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

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

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

Spring Security認(rèn)證流程的示例分析

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

前臺(Filter)

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

Spring Security為我們提供了3個已經(jīng)實現(xiàn)的Filter。UsernamePasswordAuthenticationFilterBasicAuthenticationFilterRememberMeAuthenticationFilter。如果不做任何個性化的配置,UsernamePasswordAuthenticationFilterBasicAuthenticationFilter會在默認(rèn)的過濾器鏈中。這兩種認(rèn)證方式也就是默認(rèn)的認(rèn)證方式。

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

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

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

AbstractAuthenticationProcessingFilter

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

UsernamePasswordAuthenticationFilter

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

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

認(rèn)證資料(Authentication)

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

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

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

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

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

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

AbstractAuthenticationTokenCredentialsContainerAuthentication的子類。實現(xiàn)了一些簡單的方法,但主要的方法還需要實現(xiàn)。該類的getName()方法的實現(xiàn)可以看到常用的principal類為UserDetails、AuthenticationPrincipalPrincial。如果有需要將對象設(shè)置為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();
}

認(rèn)證管理部門(AuthenticationManager)

AuthenticationManager是一個接口,認(rèn)證Authentication,如果認(rèn)證通過之后,返回的Authentication應(yīng)該帶上該principal所具有的GrantedAuthority

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

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

  • DisabledException:賬號不可用

  • LockedException:賬號被鎖

  • BadCredentialsException:證書不正確

Spring Security提供一個默認(rèn)的實現(xiàn)ProviderManager。認(rèn)證管理部門(ProviderManager)僅執(zhí)行管理職能,具體的認(rèn)證職能由認(rèn)證部門(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, 檢查是否由該認(rèn)證部門進行認(rèn)證`AuthenticationProvider`
      if (!provider.supports(toTest)) {
        continue;
      }

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

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

        if (result != null) {
          copyDetails(authentication, result);
          // #3,認(rèn)證通過則不再進行下一個認(rèn)證部門的認(rèn)證,否則拋出的異常被捕獲,執(zhí)行下一個認(rèn)證部門(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, 如果認(rèn)證通過,執(zhí)行認(rèn)證通過之后的操作
    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,如果認(rèn)證不通過,必然有拋出異常,否則表示沒有配置相應(yīng)的認(rèn)證部門(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;
  }
  ...
}

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

當(dāng)使用到Spring Security OAuth3的時候,會看到另一個實現(xiàn)OAuth3AuthenticationManager

認(rèn)證部門(AuthenticationProvider)

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

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

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

AbastractUserDetailsAuthenticationProvider

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

// 獲取 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的默認(rèn)實現(xiàn)為DefaultPreAuthenticationChecks,負責(zé)完成校驗:

  1. UserDetails#isAccountNonLocked()

  2. UserDetails#isEnabled()

  3. UserDetails#isAccountNonExpired()

postAuthenticationChecks的默認(rèn)實現(xiàn)為DefaultPostAuthenticationChecks,負責(zé)完成校驗:

UserDetails#user.isCredentialsNonExpired()

additionalAuthenticationChecks需要由實現(xiàn)類完成。

校驗成功之后,AbstractUserDetailsAuthenticationProvider會創(chuàng)建并返回一個通過認(rèn)證的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抽象方法的實現(xiàn)。

// 檢查密碼是否正確
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接口提供認(rèn)證過程所需的UserDetails的類,如DaoAuthenticationProvider需要一個UserDetailsService實例。

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

Spring Security提供了兩個UserDetailsService的實現(xiàn):InMemoryUserDetailsManagerJdbcUserDetailsManagerInMemoryUserDetailsManager為默認(rèn)配置,從UserDetailsServiceAutoConfiguration的配置中可以看出。當(dāng)然也不容易理解,基于數(shù)據(jù)庫的實現(xiàn)需要增加數(shù)據(jù)庫的配置,不適合做默認(rèn)實現(xiàn)。這兩個類均為UserDetailsManager的實現(xiàn)類,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,可以考慮實現(xiàn)UserDetailsService或者UserDetailsManager。

增加一個認(rèn)證流程

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

Spring Security認(rèn)證流程的示例分析

以上是“Spring Security認(rèn)證流程的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

分享文章:SpringSecurity認(rèn)證流程的示例分析
網(wǎng)址分享:http://m.rwnh.cn/article0/jijhio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、企業(yè)建站、用戶體驗、網(wǎng)站建設(shè)、、全網(wǎng)營銷推廣

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運營
泽库县| 抚顺县| 武平县| 怀化市| 焦作市| 屯昌县| 武胜县| 红原县| 随州市| 扶绥县| 绥中县| 绵竹市| 准格尔旗| 当阳市| 潮安县| 偏关县| 陆良县| 天台县| 浪卡子县| 府谷县| 五家渠市| 清新县| 台前县| 同心县| 鄄城县| 云南省| 峨眉山市| 兴安盟| 吴川市| 揭西县| 南华县| 萍乡市| 井冈山市| 邵东县| 石泉县| 华阴市| 香河县| 长武县| 江北区| 菏泽市| 射阳县|