Spring Security(5):認証プロセスのソースコード分析

これまでの研究から多くのファンクションポイントを学ぶことができるはずですが、それを実装する方法しか知りません。最下層は完全にブラックボックス状態であり、これらのナレッジポイントは非常に断片化されています。この記事では認証プロセスを分析します。の基礎となるソースコードは、認証プロセス全体を直列に接続します。

知識レビュー

認証プロセスの入門的実践:

機能概要

特徴 成し遂げる
ユーザー情報取得ロジックの処理 UserDetailsS​​erviceインターフェースを実装する
ユーザー検証ロジックの処理 UserDetailsインターフェースを実装する
パスワードの暗号化と復号化 PasswordEncoderインターフェースを実装する
カスタムログインの正常な処理 AuthenticationSuccessHandlerインターフェースを実装します
カスタムログイン失敗の処理 AuthenticationFailHandlerインターフェースを実装します

認証プロセスのソースコード

ファンダメンタル

  • スプリングセキュリティの基本原則
    処理フローチャート

認証プロセス

  • 認証フローチャート
    認証フローチャート
  • 最初のステップ:最初に入力UsernamePasswordAuthenticationFilterし、フロントエンドから渡されたユーザー名とパスワードを取得し、次にユーザー名とパスワードを使用してUsernamePasswordAuthenticationTokenオブジェクト(無許可)を構築します。そのクラスはAuthentication実装であり、インターフェイスは認証情報をカプセル化し、最後に図に示すように、AuthenticationManagerこのauthenticateメソッドはと呼ばれます。2番目のステップでは、その役割は3番目のステップを管理することAuthenticationProviderです。
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    
    
        if (this.postOnly && !request.getMethod().equals("POST")) {
    
    
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
    
    
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
    
    
                username = "";
            }

            if (password == null) {
    
    
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  • ステップ2:authenticateメソッドが配置ProviderManagerされているクラスを入力AuthenticationManagerます。これはインターフェイスを実装します。メソッド内にすべてを取得するためのforループがあります。ループAuthenticationProviderはなぜですか?異なる認証ロジックが異なるため、AuthenticationManagerすべての認証を収集する責任があります。次に、それぞれがループに移動して、メソッドによって渡されたログインメソッドがサポートされているかどうかを判断します。サポートされている場合は、認証プロセスが実行されauthenticateます。つまり、プロバイダーのメソッドが呼び出されます。
	while(var6.hasNext()) {
    
    
        AuthenticationProvider provider = (AuthenticationProvider)var6.next();
        if (provider.supports(toTest)) {
    
    
            if (debug) {
    
    
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
    
    
                result = provider.authenticate(authentication);
                if (result != null) {
    
    
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var11) {
    
    
                this.prepareException(var11, authentication);
                throw var11;
            } catch (InternalAuthenticationServiceException var12) {
    
    
                this.prepareException(var12, authentication);
                throw var12;
            } catch (AuthenticationException var13) {
    
    
                lastException = var13;
            }
        }
    }
  • 3番目のステップ:実際AbstractUserDetailsAuthenticationProviderにはこの抽象クラスのメソッドが呼び出されます。このメソッドはretrieveUser、クラスによってDaoAuthenticationProvider実装されるメソッドを呼び出します。このメソッドには、this.getUserDetailsService().loadUserByUsername(username);実際UserDetailsServiceにそれを取得するためのインターフェイス実装と呼ばれる文がありますUserDetails。これは、カスタム認証ロジックに関連しています。
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    
    
        UserDetails loadedUser;
        try {
    
    
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        } catch (UsernameNotFoundException var6) {
    
    
            if (authentication.getCredentials() != null) {
    
    
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
            }

            throw var6;
        } catch (Exception var7) {
    
    
            throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
        }

        if (loadedUser == null) {
    
    
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
    
    
            return loadedUser;
        }
    }
  • ステップ4:前のステップは独自のloadUserByUsernameメソッドを呼び出し、3番目のステップに戻ってUserDetailsオブジェクトを取得します。引き続きダウンし、this.preAuthenticationChecks.check(user);事前チェックを実行します。内部のロジックはUserDetails、アカウントがロックされているかどうかなど、いくつかのブール属性を判断することです。使用可能か期限切れかに関係なく、いずれかが満たされると、対応する例外がスローされます。事前チェックの後、additionalAuthenticationChecksメソッドが呼び出されて追加のチェックが行われます。追加のチェックでは、主にパスワードが一致するかどうかがチェックされます。事前チェックの後、this.postAuthenticationChecks.check(user);最後に事後チェックが行われます。このチェックは、UserDetails最後のブール属性を判別し、認証情報の有効期限が切れているかどうかをチェックするためのものです。
	try {
    
    
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
    
    
            if (!cacheWasUsed) {
    
    
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
    
    
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
    
    
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
  • ステップ5:に基づいてすべてのチェックの後、authenticationおよびuserの成功の作成Authentication1内部で再作成したUsernamePasswordAuthenticationToken(許可を)が、以前と同じではないと認証情報を入力します渡します3つのパラメータのコンストラクタを呼んでいる、と属性が設定および認証されました。
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
    
    
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }
  • ステップ6:メソッドに戻りAbstractAuthenticationProcessingFilterdoFilterメソッドを呼び出し、attemptAuthentication上記のプロセスを実行して認証を成功させたAuthentication後、successfulAuthenticationメソッドが呼び出されます。最後のステップは、this.successHandler.onAuthenticationSuccess(request, response, authResult);実際に自分で作成したログイン成功ハンドラーを呼び出すことです。
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    
    
        if (this.logger.isDebugEnabled()) {
    
    
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
    
    
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • ステップ7:上記のステップのいずれかで例外がスローされる限り、例外はキャッチされ、キャプチャ後に呼び出されます。this.unsuccessfulAuthentication(request, response, var8);内部の処理ロジックthis.failureHandler.onAuthenticationFailure(request, response, failed);は、自分で作成したログイン失敗ハンドラーを呼び出すことです。
	Authentication authResult;
    try {
    
    
        authResult = this.attemptAuthentication(request, response);
        if (authResult == null) {
    
    
            return;
        }

        this.sessionStrategy.onAuthentication(authResult, request, response);
    } catch (InternalAuthenticationServiceException var8) {
    
    
        this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
        this.unsuccessfulAuthentication(request, response, var8);
        return;
    } catch (AuthenticationException var9) {
    
    
        this.unsuccessfulAuthentication(request, response, var9);
        return;
    }

認証結果が複数のリクエスト間で共有される方法

考え?

  • まず、複数のリクエスト間の共有をセッションに入れる必要があります。SpringSecurityがセッションに何かを入れたのはいつですか、セッションから読み取ったのはいつですか。

認証情報アクセス

  • 認証プロセスのフォローアップ図
    フォローアップ画像
  • 実際、認証が成功した後の上記のsuccessfulAuthenticationメソッドでは、この行SecurityContextHolder.getContext().setAuthentication(authResult);Authentication成功した認証SecurityContextインターフェイスの実装クラスSecurityContextImplに保存してから、それを保存することSecurityContextHolderです。
  • では、誰がSecurityContextHolderそれを使用するのでしょうか?実際にはSecurityContextPersistenceFilter、それは最後の段階で、我々は前述のフィルタ接続の最上部にあるフィルタは、;。それは2つの機能を持っているリクエストが来た場合、セッションがあるかどうかを確認するためにフィルタを進めるSecurityContextならば、そこに1です。セッションから削除し、SecurityContextそれを取り出し、スレッドに入れ、フィルター・チェーンが終了すると、要求が出て行くとき、それは再びフィルターを通過します。この時点では、それが存在するかどうかをチェックします。スレッド、そしてある場合はそれを取り出してセッションに入れます。
  • この場合、リクエスト全体が1つのスレッドで完了するため、異なるリクエストがセッションで同じ認証情報を取得し、取得後にスレッドに配置することができます。したがって、SecurityContextHolder.getContext()認証情報を取得するためにスレッド内でいつでも使用できます
  • 基本原理の完全な図
    フィルターチェーン図

認証されたユーザー情報を取得する

方法1

	@GetMapping("/me")
	public Object getCurrentUser() {
    
    
		return SecurityContextHolder.getContext().getAuthentication();
	}

方法2

	@GetMapping("/me")
	public Object getCurrentUser(Authentication authentication) {
    
    
		return authentication;
	}

UserDetailsのみを取得する

	@GetMapping("/me")
	public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
    
    
		return userDetails;
	}

おすすめ

転載: blog.csdn.net/qq_36221788/article/details/105871472