ステップバイステップの Snow Spring Security パート 4、ログインプロセスはどのようなものですか? ログインユーザー情報はどこに保存されますか?

おそらく多くの人が Spring Security に連絡した後、ログインプロセスがどのようなものかを知りたいと考えています。

今日、ハンギング アークでは、全員がログイン プロセスのソース コードのコートを剥ぎ取り、裸のソース コードを通じて Spring Security のログイン プロセスを分析します。

レビュー

「Spring セキュリティを段階的に学ぶ、ログイン ページをカスタマイズする方法」の第 3 章にいます。ログイン コールバック」でカスタム ログイン ページを調査したところ、送信されたデフォルトのログイン ユーザー名とパスワードのパラメーターがコード クラス UsernamePasswordAuthenticationFilter にハードコーディングされており、コード構成を通じて変更することもできることがわかりました。ログインによって送信されたユーザー名とパスワードは UsernamePasswordAuthenticationFilter クラスで定義されているため、対応するログイン検証もここで検証する必要があります。

ログインプロセスを確認する

UsernamePasswordAuthenticationFilter のattemptAuthentication検証メソッドを調べる

UsernamePasswordAuthenticationFilter クラスはフィルターです。コードは多くないので、見つけるのは難しくありません。attemptAuthentication(HttpServletRequest request, HttpServletResponse response) は、ログインを検証するためのインターフェイスです。「Spring Security をステップバイステップで学ぶ」の第 3 章では、その方法を説明します。ログインページをカスタマイズするには?」「ログイン コールバック」プロジェクトに基づいて、ブレークポイントを作成し、ログイン プロセスを実行し、それが真であり、推測されたものであることを確認します。
ここに画像の説明を挿入
見つけるのは難しくありません。

  • まず、ログイン インターフェイスが POST リクエストであるかどうかを確認します。そうでない場合は、例外を直接スローします。もちろん、これがデフォルトです。取得リクエストをサポートしたい場合は、フィルタ UsernamePasswordAuthenticationFilter を書き換えて、postOnly 属性を FALSE に設定する必要があります
  • リクエスト内のユーザー名/パスワードは、obtainUsername メソッドと getPassword メソッドによって抽出されます。抽出メソッドは request.getParameter です。そのため、Spring Security のデフォルトのフォーム ログインでは、JSON パラメータではなくキー/値の形式でパラメータを渡す必要があります。 JSON パラメータを渡す場合は、ここでロジックを変更します
    ここに画像の説明を挿入
  • ログインによって送信されたユーザー名とパスワードを使用して UsernamePasswordAuthenticationToken オブジェクトを構築すると、構築されたオブジェクト isAuthenticated() は false を返します。
    ここに画像の説明を挿入
  • クライアントIP、sessionIdなどのクライアント情報を設定します。
    ここに画像の説明を挿入
    ここに画像の説明を挿入

ここに画像の説明を挿入

  • this.getAuthenticationManager().authenticate(authRequest) でユーザーのログインを確認し、ユーザー名、パスワード、ユーザーのステータスなどを確認し、すべて OK であればログイン成功、そうでなければログイン失敗となります。

AuthenticationManager#authenticate(authRequest) 認証プロセスを探索する

前の tryAuthentication メソッドでは、このメソッドの最後のステップで検証が開始されます。まず、AuthenticationManager (ProviderManager) を取得する必要があるため、ProviderManager の認証メソッドに入ります。このメソッドは比較的長いです。キーポイントを投稿します。分析用

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
		Class<? extends Authentication> toTest = authentication.getClass();
		for (AuthenticationProvider provider : getProviders()) {
    
    
			if (!provider.supports(toTest)) {
    
    
				continue;
			}
			try {
    
    
				result = provider.authenticate(authentication);
				if (result != null) {
    
    
					copyDetails(authentication, result);
					break;
				}
			}
		}
		if (result == null && this.parent != null) {
    
    
			try {
    
    
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
    
    }
			catch (AuthenticationException ex) {
    
    
				parentException = ex;
				lastException = ex;
			}
		}
		...
		throw lastException;
	}

ほぼすべての認証ロジックが
分析のためにここにあります。

  • まず認証のクラスを取得し、現在のプロバイダが認証をサポートしているかどうかを判断します。サポートしている場合は、プロバイダーの authenticate メソッドを呼び出して検証を開始し、検証が完了すると、新しい Authentication が返されます。このメソッドの具体的なロジックについては後ほど説明します。
  • ここには複数のプロバイダーが存在する可能性があります。プロバイダーの認証メソッドが正常に認証を返せない場合は、プロバイダーの親の認証メソッドを呼び出して検証を続行します。
  • copyDetails メソッドは、古いトークンの詳細プロパティを新しいトークンにコピーするために使用されます。
  • 次に、eraseCredentials メソッドが呼び出され、資格情報 (パスワード) が消去されます。この消去メソッドは比較的単純で、トークン内の資格情報属性を空にします。
  • 最後に、ログイン成功イベントが、publishAuthenticationSuccess メソッドを通じてブロードキャストされます。

大まかな処理は上記の通りで、for ループ内で初めて取得したプロバイダは AnonymousAuthenticationProvider ですが、このプロバイダは UsernamePasswordAuthenticationToken をサポートしていないため、provider.supports メソッドで直接 false を返し、for ループを終了します。次の if で、親の認証メソッドを直接呼び出して検証します。そして親は ProviderManager なので、再びこの認証メソッドに戻ります。もう一度認証メソッドに戻ると、プロバイダーも DaoAuthenticationProvider になりました。このプロバイダーは UsernamePasswordAuthenticationToken をサポートしているため、このクラスの認証メソッドにスムーズに入って実行されます。DaoAuthenticationProvider は AbstractUserDetailsAuthenticationProvider から継承し、認証メソッドをオーバーライドしません。 AbstractUserDetailsAuthenticationProvider#authenticate メソッドへ:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
    
    
			cacheWasUsed = false;
			try {
    
    
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
    
    
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
    
    
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
		}
		try {
    
    
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
    
    
			if (!cacheWasUsed) {
    
    
				throw ex;
			}
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
    
    
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
    
    
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

ここでのロジックは比較的単純です。

  • まず、Authentication からログイン ユーザー名を抽出します。

  • 次に、ユーザー名を指定してretrieveUserメソッドを呼び出して、現在のユーザーオブジェクトを取得します。
    ここに画像の説明を挿入

  • 次に、preAuthenticationChecks.check メソッドを呼び出して、ユーザーの各アカウントのステータス属性 (アカウントが無効かどうか、アカウントがロックされているかどうか、アカウントの有効期限が切れているかどうかなど) が正常であるかどうかを確認します。

  • addedAuthenticationChecks メソッドはパスワードを比較するためのもので、多くの友人が暗号化後の Spring Security のパスワードを比較する方法に興味を持っています。
    ここに画像の説明を挿入

  • postAuthenticationChecks.check メソッドでパスワードの有効期限が切れているかどうかを確認します。
    ここに画像の説明を挿入

  • 認証のプリンシパル属性を強制的に文字列に設定するかどうかを指定する、forcePrincipalAsString 属性があります。デフォルトは false です。通常、これを変更する必要はありません。false を使用するだけで、はるかに便利です。後で現在のユーザー情報を取得します。

  • 最後に、createSuccessAuthentication メソッドを使用して新しい UsernamePasswordAuthenticationToken を構築します。
    OK、ログイン認証プロセスは完了しました
    ここに画像の説明を挿入

ユーザーがログインした後の情報はどのように保存されますか?

ログインしているユーザー情報を見つけるには、まず問題を解決する必要があります。つまり、UsernamePasswordAuthenticationFilter #attemptAuthentication ログイン検証プロセスについてはたくさん説明しましたが、attemptAuthentication はどこでトリガーされるのでしょうか。

実際には、それについて考える必要はありません。ログイン検証はインターセプターまたはフィルターによってトリガーされる必要があります。UsernamePasswordAuthenticationFilter は本来フィルターですが、その上に親クラス AbstractAuthenticationProcessingFilter があります。ログイン検証をカスタマイズしたい場合はよくあります。 Spring Security のコード または、ログインパラメータを JSON に変更する場合、AbstractAuthenticationProcessingFilter を継承するようにフィルターをカスタマイズする必要があります。UsernamePasswordAuthenticationFilter#attemptAuthentication メソッドが AbstractAuthenticationProcessingFilter クラスの doFilter メソッドでトリガーされることは間違いありません。 attemptAuthentication
ここに画像の説明を挿入
メソッドが呼び出されるとき、実際に UsernamePasswordAuthenticationFilter#attemptAuthentication メソッドがトリガーされます。ログインで例外がスローされると、unsuccessfulAuthentication メソッドが呼び出され、ログインが成功すると、successfulAuthentication メソッドが呼び出されます。成功した認証メソッドで:

ここに画像の説明を挿入
SecurityContextHolder.getContext().setAuthentication(authResult); という非常に重要なコード行があり、成功したログインのユーザー情報がここに保存されます。つまり、ユーザーのログイン情報を取得したい場合はどこにでも保存されます。 SecurityContextHolder.getContext() から取得できます。変更したい場合は、ここで変更することもできます。

最後に、successHandler.onAuthenticationSuccess があることもわかります。これは、SecurityConfig で構成したログイン成功コールバック メソッドであり、ここでトリガーされます。

ソースコードを分析したところ、同じであることがわかりました

おすすめ

転載: blog.csdn.net/huangxuanheng/article/details/119082778