通過檢視原始碼可得知:
##1. 前言 抽象類中AbstractUserDetailsAuthenticationProvider 介面丟擲異常AuthenticationException
下面原始碼註釋這麼描述
*
* @throws AuthenticationException if the credentials could not be validated
* (generally a <code>BadCredentialsException</code>, an
* <code>AuthenticationServiceException</code> or
* <code>UsernameNotFoundException</code>)
*/
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
複製程式碼
AuthenticationException 丟擲的情況是 BadCredentialsException,AuthenticationServiceException,UsernameNotFoundException這三個異常。
當UserNameNotFoundException 這個異常的情況下會丟擲
可實際情況下我們 查詢的user為null 丟擲了 UserNameNotFoundException 這個異常但是實際並沒有丟擲來,丟擲的是 AuthenticationException
通過繼續往下檢視原始碼後明白了,原來是做了對UserNameNotFoundException 處理,轉換成了AuthenticationException 這個異常;
hideUserNotFoundExceptions = true;
...
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
複製程式碼
所以我們沒有丟擲UsernameNotFoundException 這個異常,而是將這個異常進行了轉換。
##2.解決辦法 如何丟擲這個異常,那就是將hideUserNotFoundExceptions 設定為 false;
####2.1設定實現類中
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(mUserDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
複製程式碼
最後在WebSecurityConfig配置即可
auth.authenticationProvider(daoAuthenticationProvider());
複製程式碼
####2.2 debug來看一下
設定之前
設定之後丟擲的UsernameNotFoundException 異常已經捕獲到了,然後進入if中 最後丟擲
new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"))
複製程式碼
會將異常資訊存入session中 , 根據key即可獲取 最後在失敗的處理器中獲取到
@Component
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public MyAuthenctiationFailureHandler() {
this.setDefaultFailureUrl("/loginPage");
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("進入認證失敗處理類");
HttpSession session = request.getSession();
AuthenticationException attributes = (AuthenticationException) session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
logger.info("aaaa " + attributes);
super.onAuthenticationFailure(request, response, exception);
}
}
複製程式碼
這樣子做可以直接在session中獲取到,如果自定義丟擲一個異常首先控制檯會報異常錯,其次前臺的通過如ajax獲取錯誤資訊,又得寫ajax。
這樣子做直接將資訊存入session中,springSecurity直接為我們封裝到session中了,可以直接根據key獲取到。
如: ${session.SPRING_SECURITY_LAST_EXCEPTION.message}
####2.4 判斷密碼
注意 如果使用者名稱不存在,拋了異常
不要再在密碼驗證其中丟擲密碼錯誤異常,不然丟擲UserNameNotFoundException 後還會驗證密碼是否正確,如果密碼正確還好,返回true,如果不正確丟擲異常。 此時會將 UsernameNotFoundException 異常覆蓋,這裡應該返回false。
原始碼如下:
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
// 、、、、、、、 這裡會去匹配密碼是否正確
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
複製程式碼
mitigateAgainstTimingAttack 方法
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
//這裡會是自定義密碼加密
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
複製程式碼
我的密碼加密器
@Override
public boolean matches(CharSequence charSequence, String s) {
String pwd = charSequence.toString();
log.info("前端傳過來密碼為: " + pwd);
log.info("加密後密碼為: " + MD5Util.encode(charSequence.toString()));
log.info("資料庫儲存的密碼: " + s);
//s 應在資料庫中加密
if( MD5Util.encode(charSequence.toString()).equals(MD5Util.encode(s))){
return true;
}
//throw new DisabledException("--密碼錯誤--"); //不能丟擲異常
return false;
}
複製程式碼
如下是 我們密碼驗證器裡丟擲異常後獲取到的異常
異常
密碼未驗證 之前捕獲到的異常資訊
驗證密碼後捕獲到的異常 (這裡跳到ProviderManager中)既然我使用者名稱不對我就不必要驗證密碼了,所以不應該丟擲異常,應該直接返回false。 不然。此處密碼異常會將 使用者不存在進行覆蓋!
##3. 驗證頁面程式碼
<body>
登入頁面
<div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION!=null?session.SPRING_SECURITY_LAST_EXCEPTION.message:''}">[...]</div>
<form method="post" action="/login" >
<input type="text" name="username" /><br>
<input type="password" name="password" />
<input type="submit" value="login" />
</form>
複製程式碼