成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【SpringSecurity系列02】SpringSecurity 表單認(rèn)證邏輯源碼解讀

zzir / 2770人閱讀

摘要:通過上面我們知道對(duì)于表單登錄的認(rèn)證請求是交給了處理的,那么具體的認(rèn)證流程如下從上圖可知,繼承于抽象類。中維護(hù)這一個(gè)對(duì)象列表,通過遍歷判斷并且最后選擇對(duì)象來完成最后的認(rèn)證。發(fā)布一個(gè)登錄事件。

概要

前面一節(jié),通過簡單配置即可實(shí)現(xiàn)SpringSecurity表單認(rèn)證功能,而今天這一節(jié)將通過閱讀源碼的形式來學(xué)習(xí)SpringSecurity是如何實(shí)現(xiàn)這些功能, 前方高能預(yù)警,本篇分析源碼篇幅較長。

過濾器鏈

前面我說過SpringSecurity是基于過濾器鏈的形式,那么我解析將會(huì)介紹一下具體有哪些過濾器。

Filter Class 介紹
SecurityContextPersistenceFilter 判斷當(dāng)前用戶是否登錄
CrsfFilter 用于防止csrf攻擊
LogoutFilter 處理注銷請求
UsernamePasswordAuthenticationFilter 處理表單登錄的請求(也是我們今天的主角)
BasicAuthenticationFilter 處理http basic認(rèn)證的請求

由于過濾器鏈中的過濾器實(shí)在太多,我沒有一一列舉,調(diào)了幾個(gè)比較重要的介紹一下。

通過上面我們知道SpringSecurity對(duì)于表單登錄的認(rèn)證請求是交給了UsernamePasswordAuthenticationFilter處理的,那么具體的認(rèn)證流程如下:

從上圖可知,UsernamePasswordAuthenticationFilter繼承于抽象類AbstractAuthenticationProcessingFilter

具體認(rèn)證是:

進(jìn)入doFilter方法,判斷是否要認(rèn)證,如果需要認(rèn)證則進(jìn)入attemptAuthentication方法,如果不需要直接結(jié)束

attemptAuthentication方法中根據(jù)username跟password構(gòu)造一個(gè)UsernamePasswordAuthenticationToken對(duì)象(此時(shí)的token是未認(rèn)證的),并且將它交給ProviderManger來完成認(rèn)證。

ProviderManger中維護(hù)這一個(gè)AuthenticationProvider對(duì)象列表,通過遍歷判斷并且最后選擇DaoAuthenticationProvider對(duì)象來完成最后的認(rèn)證。

DaoAuthenticationProvider根據(jù)ProviderManger傳來的token取出username,并且調(diào)用我們寫的UserDetailsService的loadUserByUsername方法從數(shù)據(jù)庫中讀取用戶信息,然后對(duì)比用戶密碼,如果認(rèn)證通過,則返回用戶信息也是就是UserDetails對(duì)象,在重新構(gòu)造UsernamePasswordAuthenticationToken(此時(shí)的token是 已經(jīng)認(rèn)證通過了的)。

接下來我們將通過源碼來分析具體的整個(gè)認(rèn)證流程。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 是一個(gè)抽象類。所有的認(rèn)證認(rèn)證請求的過濾器都會(huì)繼承于它,它主要將一些公共的功能實(shí)現(xiàn),而具體的驗(yàn)證邏輯交給子類實(shí)現(xiàn),有點(diǎn)類似于父類設(shè)置好認(rèn)證流程,子類負(fù)責(zé)具體的認(rèn)證邏輯,這樣跟設(shè)計(jì)模式的模板方法模式有點(diǎn)相似。

現(xiàn)在我們分析一下 它里面比較重要的方法

1、doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        // 省略不相干代碼。。。
    // 1、判斷當(dāng)前請求是否要認(rèn)證
        if (!requiresAuthentication(request, response)) {
      // 不需要直接走下一個(gè)過濾器
            chain.doFilter(request, response);
            return;
        }
        try {
      // 2、開始請求認(rèn)證,attemptAuthentication具體實(shí)現(xiàn)給子類,如果認(rèn)證成功返回一個(gè)認(rèn)證通過的Authenticaion對(duì)象
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                return;
            }
      // 3、登錄成功 將認(rèn)證成功的用戶信息放入session SessionAuthenticationStrategy接口,用于擴(kuò)展
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        catch (InternalAuthenticationServiceException failed) {
      //2.1、發(fā)生異常,登錄失敗,進(jìn)入登錄失敗handler回調(diào)
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        catch (AuthenticationException failed) {
      //2.1、發(fā)生異常,登錄失敗,進(jìn)入登錄失敗處理器
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        // 3.1、登錄成功,進(jìn)入登錄成功處理器。
        successfulAuthentication(request, response, chain, authResult);
    }
2、successfulAuthentication

登錄成功處理器

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
    //1、登錄成功 將認(rèn)證成功的Authentication對(duì)象存入SecurityContextHolder中
    //      SecurityContextHolder本質(zhì)是一個(gè)ThreadLocal
        SecurityContextHolder.getContext().setAuthentication(authResult);
    //2、如果開啟了記住我功能,將調(diào)用rememberMeServices的loginSuccess 將生成一個(gè)token
      //   將token放入cookie中這樣 下次就不用登錄就可以認(rèn)證。具體關(guān)于記住我rememberMeServices的相關(guān)分析我                    們下面幾篇文章會(huì)深入分析的。
        rememberMeServices.loginSuccess(request, response, authResult);
        // Fire event
    //3、發(fā)布一個(gè)登錄事件。
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
    //4、調(diào)用我們自己定義的登錄成功處理器,這樣也是我們擴(kuò)展得知登錄成功的一個(gè)擴(kuò)展點(diǎn)。
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }
3、unsuccessfulAuthentication

登錄失敗處理器

protected void unsuccessfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
    //1、登錄失敗,將SecurityContextHolder中的信息清空
        SecurityContextHolder.clearContext();
    //2、關(guān)于記住我功能的登錄失敗處理
        rememberMeServices.loginFail(request, response);
    //3、調(diào)用我們自己定義的登錄失敗處理器,這里可以擴(kuò)展記錄登錄失敗的日志。
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

關(guān)于AbstractAuthenticationProcessingFilter主要分析就到這。我們可以從源碼中知道,當(dāng)請求進(jìn)入該過濾器中具體的流程是

判斷該請求是否要被認(rèn)證

調(diào)用attemptAuthentication方法開始認(rèn)證,由于是抽象方法具體認(rèn)證邏輯給子類

如果登錄成功,則將認(rèn)證結(jié)果Authentication對(duì)象根據(jù)session策略寫入session中,將認(rèn)證結(jié)果寫入到SecurityContextHolder,如果開啟了記住我功能,則根據(jù)記住我功能,生成token并且寫入cookie中,最后調(diào)用一個(gè)successHandler對(duì)象的方法,這個(gè)對(duì)象可以是我們配置注入的,用于處理我們的自定義登錄成功的一些邏輯(比如記錄登錄成功日志等等)。

如果登錄失敗,則清空SecurityContextHolder中的信息,并且調(diào)用我們自己注入的failureHandler對(duì)象,處理我們自己的登錄失敗邏輯。

UsernamePasswordAuthenticationFilter

從上面分析我們可以知道,UsernamePasswordAuthenticationFilter是繼承于AbstractAuthenticationProcessingFilter,并且實(shí)現(xiàn)它的attemptAuthentication方法,來實(shí)現(xiàn)認(rèn)證具體的邏輯實(shí)現(xiàn)。接下來,我們通過閱讀UsernamePasswordAuthenticationFilter的源碼來解讀,它是如何完成認(rèn)證的。 由于這里會(huì)涉及UsernamePasswordAuthenticationToken對(duì)象構(gòu)造,所以我們先看看UsernamePasswordAuthenticationToken的源碼

1、UsernamePasswordAuthenticationToken
// 繼承至AbstractAuthenticationToken 
// AbstractAuthenticationToken主要定義一下在SpringSecurity中toke需要存在一些必須信息
// 例如權(quán)限集合  Collection authorities; 是否認(rèn)證通過boolean authenticated = false;認(rèn)證通過的用戶信息Object details;
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
 
  // 未登錄情況下 存的是用戶名 登錄成功情況下存的是UserDetails對(duì)象
    private final Object principal;
  // 密碼
    private Object credentials;

  /**
  * 構(gòu)造函數(shù),用戶沒有登錄的情況下,此時(shí)的authenticated是false,代表尚未認(rèn)證
  */
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

 /**
  * 構(gòu)造函數(shù),用戶登錄成功的情況下,多了一個(gè)參數(shù) 是用戶的權(quán)限集合,此時(shí)的authenticated是true,代表認(rèn)證成功
  */
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
            Collection authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }
}

接下來我們就可以分析attemptAuthentication方法了。

2、attemptAuthentication
public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
     // 1、判斷是不是post請求,如果不是則拋出AuthenticationServiceException異常,注意這里拋出的異常都在AbstractAuthenticationProcessingFilter#doFilter方法中捕獲,捕獲之后會(huì)進(jìn)入登錄失敗的邏輯。
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
    // 2、從request中拿用戶名跟密碼
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        // 3、非空處理,防止NPE異常
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
    // 4、除去空格
        username = username.trim();
    // 5、根據(jù)username跟password構(gòu)造出一個(gè)UsernamePasswordAuthenticationToken對(duì)象 從上文分析可知道,此時(shí)的token是未認(rèn)證的。
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
    // 6、配置一下其他信息 ip 等等
        setDetails(request, authRequest);
   //  7、調(diào)用ProviderManger的authenticate的方法進(jìn)行具體認(rèn)證邏輯
        return this.getAuthenticationManager().authenticate(authRequest);
    }
ProviderManager

維護(hù)一個(gè)AuthenticationProvider列表,進(jìn)行認(rèn)證邏輯驗(yàn)證

1、authenticate
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    // 1、拿到token的類型。
        Class toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
   // 2、遍歷AuthenticationProvider列表
        for (AuthenticationProvider provider : getProviders()) {
      // 3、AuthenticationProvider不支持當(dāng)前token類型,則直接跳過
            if (!provider.supports(toTest)) {
                continue;
            }

            try {
        // 4、如果Provider支持當(dāng)前token,則交給Provider完成認(rèn)證。
                result = provider.authenticate(authentication);
     
            }
            catch (AccountStatusException e) {

                throw e;
            }
            catch (InternalAuthenticationServiceException e) {
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }
    // 5、登錄成功 返回登錄成功的token
        if (result != null) {
            eventPublisher.publishAuthenticationSuccess(result);
            return result;
        }

    }
AbstractUserDetailsAuthenticationProvider 1、authenticate

AbstractUserDetailsAuthenticationProvider實(shí)現(xiàn)了AuthenticationProvider接口,并且實(shí)現(xiàn)了部分方法,DaoAuthenticationProvider繼承于AbstractUserDetailsAuthenticationProvider類,所以我們先來看看AbstractUserDetailsAuthenticationProvider的實(shí)現(xiàn)。

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {

  // 國際化處理
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();


    /**
     * 對(duì)token一些檢查,具體檢查邏輯交給子類實(shí)現(xiàn),抽象方法
     */
    protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException;


  /**
     * 認(rèn)證邏輯的實(shí)現(xiàn),調(diào)用抽象方法retrieveUser根據(jù)username獲取UserDetails對(duì)象
     */
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    
    // 1、獲取usernmae
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

    // 2、嘗試去緩存中獲取UserDetails對(duì)象
        UserDetails user = this.userCache.getUserFromCache(username);
    // 3、如果為空,則代表當(dāng)前對(duì)象沒有緩存。
        if (user == null) {
            cacheWasUsed = false;
            try {
        //4、調(diào)用retrieveUser去獲取UserDetail對(duì)象,為什么這個(gè)方法是抽象方法大家很容易知道,如果UserDetail信息存在關(guān)系數(shù)據(jù)庫 則可以重寫該方法并且去關(guān)系數(shù)據(jù)庫獲取用戶信息,如果UserDetail信息存在其他地方,可以重寫該方法用其他的方法去獲取用戶信息,這樣絲毫不影響整個(gè)認(rèn)證流程,方便擴(kuò)展。
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
      catch (UsernameNotFoundException notFound) {
                
                // 捕獲異常 日志處理 并且往上拋出,登錄失敗。
                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }
        }

        try {
      // 5、前置檢查  判斷當(dāng)前用戶是否鎖定,禁用等等
            preAuthenticationChecks.check(user);
      // 6、其他的檢查,在DaoAuthenticationProvider是檢查密碼是否一致
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
        
        }

    // 7、后置檢查,判斷密碼是否過期
        postAuthenticationChecks.check(user);

     
        // 8、登錄成功通過UserDetail對(duì)象重新構(gòu)造一個(gè)認(rèn)證通過的Token對(duì)象
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        // 調(diào)用第二個(gè)構(gòu)造方法,構(gòu)造一個(gè)認(rèn)證通過的Token對(duì)象
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());

        return result;
    }

}

接下來我們具體看看retrieveUser的實(shí)現(xiàn),沒看源碼大家應(yīng)該也可以知道,retrieveUser方法應(yīng)該是調(diào)用UserDetailsService去數(shù)據(jù)庫查詢是否有該用戶,以及用戶的密碼是否一致。

DaoAuthenticationProvider

DaoAuthenticationProvider 主要是通過UserDetailService來獲取UserDetail對(duì)象。

1、retrieveUser
protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        try {
      // 1、調(diào)用UserDetailsService接口的loadUserByUsername方法獲取UserDeail對(duì)象
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
       // 2、如果loadedUser為null 代表當(dāng)前用戶不存在,拋出異常 登錄失敗。
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
      // 3、返回查詢的結(jié)果
            return loadedUser;
        }
    }
2、additionalAuthenticationChecks
protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
    // 1、如果密碼為空,則拋出異常、
        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

    // 2、獲取用戶輸入的密碼
        String presentedPassword = authentication.getCredentials().toString();

    // 3、調(diào)用passwordEncoder的matche方法 判斷密碼是否一致
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

      // 4、如果不一致 則拋出異常。
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }
總結(jié)

至此,整認(rèn)證流程已經(jīng)分析完畢,大家如果有什么不懂可以關(guān)注我的公眾號(hào)一起討論。

學(xué)習(xí)是一個(gè)漫長的過程,學(xué)習(xí)源碼可能會(huì)很困難但是只要努力一定就會(huì)有獲取,大家一致共勉。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74129.html

相關(guān)文章

  • SpringSecurity01(springSecurity執(zhí)行流程02)

    摘要:里面配置的過濾器鏈當(dāng)用戶使用表單請求時(shí)進(jìn)入返回一個(gè)的實(shí)例一般是從數(shù)據(jù)庫中查詢出來的實(shí)例然后直接到最后一個(gè)如果有錯(cuò)則拋錯(cuò)給前面一個(gè)進(jìn)行拋錯(cuò)如果沒有錯(cuò)則放行可以訪問對(duì)應(yīng)的資源上面是總的執(zhí)行流程下面單獨(dú)說一下的認(rèn)證流程這個(gè)圖應(yīng)該都看得懂和里面的配 showImg(https://segmentfault.com/img/bVbvO0O?w=1258&h=261);web.xml里面配置的過濾...

    Dr_Noooo 評(píng)論0 收藏0
  • SpringSecurity系列01】初識(shí)SpringSecurity

    摘要:什么是是一個(gè)能夠?yàn)榛诘钠髽I(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。它來自于,那么它與整合開發(fā)有著天然的優(yōu)勢,目前與對(duì)應(yīng)的開源框架還有。通常大家在做一個(gè)后臺(tái)管理的系統(tǒng)的時(shí)候,應(yīng)該采用判斷用戶是否登錄。 ? 什么是SpringSecurity ? ? Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全...

    elva 評(píng)論0 收藏0
  • 前后端分離項(xiàng)目 — 基于SpringSecurity OAuth2.0用戶認(rèn)證

    摘要:前言現(xiàn)在的好多項(xiàng)目都是基于移動(dòng)端以及前后端分離的項(xiàng)目,之前基于的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于的微服務(wù)架構(gòu)以及單頁面應(yīng)用流行起來后,情況更甚。使用生成是什么請自行百度。 1、前言 現(xiàn)在的好多項(xiàng)目都是基于APP移動(dòng)端以及前后端分離的項(xiàng)目,之前基于Session的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于SpringCloud的微服務(wù)架構(gòu)以及...

    QLQ 評(píng)論0 收藏0
  • springSecurity02(mybatis+springmvc+spring) 01

    摘要:建立一個(gè)模塊繼承上一個(gè)模塊然后添加依賴解決打包時(shí)找不到文件建立數(shù)據(jù)源文件數(shù)據(jù)庫連接相關(guān)修改配置數(shù)據(jù)源和整合,以及事務(wù)管理自動(dòng)掃描掃描時(shí)跳過注解的類控制器掃描配置文件這里指向的是 1.建立一個(gè)模塊繼承上一個(gè)模塊然后添加依賴 junit junit 4.11 test ...

    FrancisSoung 評(píng)論0 收藏0
  • SpringSecurity01(使用傳統(tǒng)的xml方式開發(fā),且不連接數(shù)據(jù)庫)

    摘要:創(chuàng)建一個(gè)工程在里面添加依賴,依賴不要隨便改我改了出錯(cuò)了好幾次都找不到原因可以輕松的將對(duì)象轉(zhuǎn)換成對(duì)象和文檔同樣也可以將轉(zhuǎn)換成對(duì)象和配置 1.創(chuàng)建一個(gè)web工程2.在pom里面添加依賴,依賴不要隨便改,我改了出錯(cuò)了好幾次都找不到原因 UTF-8 1.7 1.7 2.5.0 1.2 3.0-alpha-1 ...

    Gilbertat 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<