これまでの研究から多くのファンクションポイントを学ぶことができるはずですが、それを実装する方法しか知りません。最下層は完全にブラックボックス状態であり、これらのナレッジポイントは非常に断片化されています。この記事では認証プロセスを分析します。の基礎となるソースコードは、認証プロセス全体を直列に接続します。
知識レビュー
認証プロセスの入門的実践:
- Spring Security(1):RESTful APIインターセプトのフィルター、インターセプター、およびスライスの概要
- Spring Security(2):SpringSecurityの初期設定と基本原則
- Spring Security(3):ユーザー定義の認証ロジック
- Spring Security(4):パーソナライズされたユーザー認証プロセス
機能概要
特徴 | 成し遂げる |
---|---|
ユーザー情報取得ロジックの処理 | UserDetailsServiceインターフェースを実装する |
ユーザー検証ロジックの処理 | 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
の成功の作成Authentication
1内部で再作成した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:メソッドに戻り
AbstractAuthenticationProcessingFilter
、doFilter
メソッドを呼び出し、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;
}