SpringSecurity認(rèn)證流程是怎樣的-創(chuàng)新互聯(lián)

本篇文章為大家展示了SpringSecurity認(rèn)證流程是怎樣的,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

成都創(chuàng)新互聯(lián)公司從2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目做網(wǎng)站、網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元楊浦做網(wǎng)站,已為上家服務(wù),為楊浦各地企業(yè)和個人服務(wù),聯(lián)系電話:18982081108

前言

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

相關(guān)版本:

java: jdk 8 spring-boot: 2.1.6.RELEASE

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

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

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

  1. Filter攔截請求,生成一個未認(rèn)證的

  2. Authentication
  3. ,交由

  4. AuthenticationManager
  5. 進(jìn)行認(rèn)證;

  6. AuthenticationManager
  7. 的默認(rèn)實(shí)現(xiàn)

  8. ProviderManager
  9. 會通過

  10. AuthenticationProvider
  11. Authentication
  12. 進(jìn)行認(rèn)證,其本身不做認(rèn)證處理;  如果認(rèn)證通過,則創(chuàng)建一個認(rèn)證通過的

  13. Authentication
  14. 返回;否則拋出異常,以表示認(rèn)證不通過。

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

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

)

請求到前臺之后,負(fù)責(zé)該請求的前臺會將請求的內(nèi)容封裝為一個Authentication對象交給認(rèn)證管理部門,認(rèn)證管理部門僅管理認(rèn)證部門,不做具體的認(rèn)證操作,具體的操作由與該前臺相關(guān)的認(rèn)證部門進(jìn)行處理。當(dāng)然,每個認(rèn)證部門需要判斷Authentication是否為該部門負(fù)責(zé),是則由該部門負(fù)責(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)證是否由該部門負(fù)責(zé),因而也許提供一個兩者相互認(rèn)同的Authentication.

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

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

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

前臺(Filter)

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

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

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

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

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

AbstractAuthenticationProcessingFilter

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

UsernamePasswordAuthenticationFilter

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

BasicAuthenticationFilter 該前臺(Filter)只會處理含有Authorization的Header,且小寫化后的值以basic開頭的請求,否則該前臺(Filter)不負(fù)責(zé)處理。該Filter會從header中獲取Base64編碼之后的username和password,創(chuàng)建UsernamePasswordAuthenticationToken提供給認(rèn)證管理部門(AuthenticationMananager)進(jìn)行認(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)通過驗(yàn)證的Authentication建議設(shè)置為一個不可修改的實(shí)例。

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

AbstractAuthenticationTokenCredentialsContainerAuthentication的子類。實(shí)現(xiàn)了一些簡單的方法,但主要的方法還需要實(shí)現(xiàn)。該類的getName()方法的實(shí)現(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;}

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

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

Spring Security提供一個默認(rèn)的實(shí)現(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)證部門進(jìn)行認(rèn)證`AuthenticationProvider`      if (!provider.supports(toTest)) {        continue;      }      if (debug) {        logger.debug("Authentication attempt using "            + provider.getClass().getName());      }      try {        // #2, 認(rèn)證部門進(jìn)行認(rèn)證        result = provider.authenticate(authentication);        if (result != null) {          copyDetails(authentication, result);          // #3,認(rèn)證通過則不再進(jì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)證部門進(jìn)行認(rèn)證認(rèn)證部門進(jìn)行認(rèn)證認(rèn)證通過則不再進(jì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的時候,會看到另一個實(shí)現(xiàn)OAuth3AuthenticationManager。

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

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

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

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

AbastractUserDetailsAuthenticationProvider

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

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

retrieveUser()方法為校驗(yàn)提供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)分為三步驗(yàn)證:

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

preAuthenticationChecks的默認(rèn)實(shí)現(xiàn)為DefaultPreAuthenticationChecks,負(fù)責(zé)完成校驗(yàn):

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

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

UserDetails#user.isCredentialsNonExpired()

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

校驗(yà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抽象方法的實(shí)現(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實(shí)例。只要實(shí)例化這兩個類,并放入到Spring容器中即可。

資料部門(UserDetailsService)

UserDetailsService接口提供認(rèn)證過程所需的UserDetails的類,如DaoAuthenticationProvider需要一個UserDetailsService實(shí)例。

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

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

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

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

我會在另一篇文章中以手機(jī)號碼+驗(yàn)證碼登錄為例進(jìn)行講解。

上述內(nèi)容就是SpringSecurity認(rèn)證流程是怎樣的,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞名稱:SpringSecurity認(rèn)證流程是怎樣的-創(chuàng)新互聯(lián)
轉(zhuǎn)載來于:http://muchs.cn/article32/hegpc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google服務(wù)器托管、企業(yè)建站手機(jī)網(wǎng)站建設(shè)、App設(shè)計(jì)小程序開發(fā)

廣告

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

成都做網(wǎng)站