Spring-security認証プロセスの分析とカスタムログイン
17174 ワード
まずspring-securityは、spring-securityがビジネスを処理する入口である認証フィルタを構成します.ユーザーがフィルタを書き直さない場合は、デフォルトのフィルタUsername PasswordAuthenticationFilterを使用します.抽象クラスA b s t r a c t u t h e n t i c a tionProcessingFilterを継承し、authenticationManagerのプロパティを注入し、securityの認証マネージャを構成します.
フィルタU s e r m e P a s s s w o r d AuthenticationFilterユーザー名パスワードを取得し、認証マネージャによる認証コードを次のように作成します.
認証マネージャの構成は次のとおりです.
認証マネージャは、インタフェースAuthenticationManagerによって処理されます.
ProviderManagerクラスはこのインタフェースを実現し、その認証コードは以下の通りである.
上記のコードを見て、認証プロセスは、認証のために抽象クラスA b s t r a c t U s e r D e tailsAuthenticationProviderを呼び出すインタフェースであるAuthenticationProviderを呼び出し続けます.
DaoAuthenticationProviderクラスは抽象クラスA b s t r a c t U s e r D e tailsAuthenticationProviderを継承し、ユーザを取得する方法を書き換え、構成中のuser-service-refにより、UserDetailsを取得する方法を呼び出す.
次に、password-encoderが設定されていない場合は、デフォルトでPlaintextPasswordEncoderを呼び出してパスワードの比較を行います.そうでない場合は、ユーザー構成のパスワード暗号化クラスを呼び出してパスワード比較を行います.
上記の分析に基づいて、私たちは以下の業務の需要があって、qqなどの第三者を通じてログインして、システムのアカウントをバインドして、アカウントをバインドすることを通じて直接securityのログインを行って、それでは私たちはどのようにしますか?主なコードは次のとおりです.
ユーザー・クエリー・データベースをバインドし、ユーザー・パスワードを取得し、ユーザー名PasswordAuthenticationTokenを手動で作成し、構成された認証マネージャを呼び出し、認証トークンを返してsecurityコンテナに登録します.できます.
ここで問題があります.user-service-refがパスワード暗号化方式を構成している場合、データベースに格納されているユーザーパスワードは暗号化されたパスワードであり、認証マネージャを呼び出すとデータベースクエリのパスワードをもう一度暗号化方式で暗号化し、パスワードの対比が失敗します.
この問題を解決するには、認証マネージャを書き換えます.
これにより、構成された認証マネージャを使用して暗号化検証を行うことはできません.
<beans:bean id="myLoginFilter" class="com.yinhai.modules.security.spring.app.filter.Ta3AuthenticationFilter">
<beans:property name="authenticationManager" ref="myAuthenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="taOnAuthenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="taAuthenticationFailureHandler" />
<beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
<beans:property name="userBpo" ref="userBpo" />
<beans:property name="sessionAuthenticationStrategy" ref="sas">beans:property>
beans:bean>
フィルタU s e r m e P a s s s w o r d AuthenticationFilterユーザー名パスワードを取得し、認証マネージャによる認証コードを次のように作成します.
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);
}
}
認証マネージャの構成は次のとおりです.
<authentication-manager alias="myAuthenticationManager">
<authentication-provider user-service-ref="taUserDetailsService">
<password-encoder ref="md5Encoder">
<salt-source ref="saltSource"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
認証マネージャは、インタフェースAuthenticationManagerによって処理されます.
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
ProviderManagerクラスはこのインタフェースを実現し、その認証コードは以下の通りである.
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
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;
}
}
}
if (result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
this.eventPublisher.publishAuthenticationSuccess(result);
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
this.prepareException((AuthenticationException)lastException, authentication);
throw lastException;
}
}
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
result = provider.authenticate(authentication);
上記のコードを見て、認証プロセスは、認証のために抽象クラスA b s t r a c t U s e r D e tailsAuthenticationProviderを呼び出すインタフェースであるAuthenticationProviderを呼び出し続けます.
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
DaoAuthenticationProviderクラスは抽象クラスA b s t r a c t U s e r D e tailsAuthenticationProviderを継承し、ユーザを取得する方法を書き換え、構成中のuser-service-refにより、UserDetailsを取得する方法を呼び出す.
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
次に、password-encoderが設定されていない場合は、デフォルトでPlaintextPasswordEncoderを呼び出してパスワードの比較を行います.そうでない場合は、ユーザー構成のパスワード暗号化クラスを呼び出してパスワード比較を行います.
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
上記の分析に基づいて、私たちは以下の業務の需要があって、qqなどの第三者を通じてログインして、システムのアカウントをバインドして、アカウントをバインドすることを通じて直接securityのログインを行って、それでは私たちはどのようにしますか?主なコードは次のとおりです.
String userName = request.getParameter("userName");
String pwd = request.getParameter("pwd");
userName = userName.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd);
Authentication authentication = authenticationManager.authenticate(authRequest);
// loadUserByUsername SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = request.getSession();
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());// ,
ユーザー・クエリー・データベースをバインドし、ユーザー・パスワードを取得し、ユーザー名PasswordAuthenticationTokenを手動で作成し、構成された認証マネージャを呼び出し、認証トークンを返してsecurityコンテナに登録します.できます.
ここで問題があります.user-service-refがパスワード暗号化方式を構成している場合、データベースに格納されているユーザーパスワードは暗号化されたパスワードであり、認証マネージャを呼び出すとデータベースクエリのパスワードをもう一度暗号化方式で暗号化し、パスワードの対比が失敗します.
この問題を解決するには、認証マネージャを書き換えます.
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
String username = auth.getName();
UserDetails userDetails = taUserDetailsService.loadUserByUsername(username);
Object principal = userDetails;
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()));
result.setDetails(auth.getDetails());
return result;
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginId, password);
Authentication authentication = simpleAuthenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
これにより、構成された認証マネージャを使用して暗号化検証を行うことはできません.