在Web應用開發中,安全一直是很是重要的一個方面。在龐大的spring生態圈中,權限校驗框架也是很是完善的。其中,spring security是很是好用的。今天記錄一下在開發中遇到的一個spring-security相關的問題。java
使用spring security進行受權登陸的時候,發現登陸接口沒法正常捕捉UsernameNotFoundException異常,捕捉到的一直是BadCredentialsException異常。咱們的預期是:web
貼幾個比較重要的代碼:redis
@Service public class AuthServiceImpl implements AuthService { @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public JwtAuthenticationResponse login(String username, String password) { //構造spring security須要的UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); //調用authenticationManager.authenticate(upToken)方法驗證 //該方法將會執行UserDetailsService的loadUserByUsername驗證用戶名 //以及PasswordEncoder的matches方法驗證密碼 val authenticate = authenticationManager.authenticate(upToken); JwtUser userDetails = (JwtUser) authenticate.getPrincipal(); val token = jwtTokenUtil.generateToken(userDetails); return new JwtAuthenticationResponse(token, userDetails.getId(), userDetails.getUsername()); } }
@Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AbstractUser abstractUser = userRepository.findByUsername(username); //若是經過用戶名找不到用戶,則拋出UsernameNotFoundException異常 if (abstractUser == null) { throw new UsernameNotFoundException(String.format("No abstractUser found with username '%s'.", username)); } else { return JwtUserFactory.create(abstractUser); } } }
try { final JwtAuthenticationResponse jsonResponse = authService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword()); //存入redis redisService.setToken(jsonResponse.getToken()); return ok(jsonResponse); } catch (BadCredentialsException e) { //捕捉到BadCredentialsException,密碼不正確 return forbidden(LOGIN_PASSWORD_ERROR, request); } catch (UsernameNotFoundException e) { //捕捉到UsernameNotFoundException,用戶名不正確 return forbidden(LOGIN_USERNAME_ERROR, request); }
catch (UsernameNotFoundException e) { return forbidden(LOGIN_USERNAME_ERROR, request); }
catch (BadCredentialsException e) { return forbidden(LOGIN_PASSWORD_ERROR, request); }
實際上,無論是拋出什麼錯,最後抓到的都是BadCredentialsExceptionspring
通過步進法跟蹤代碼,發現問題所在,位於數據庫
AbstractUserDetailsAuthenticationProvider public Authentication authenticate(Authentication authentication)
既然已經找到了是由於hideUserNotFoundExceptions = true
致使的問題,那把hideUserNotFoundExceptions = false
不就完事了嗎?json
@Bean public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); daoAuthenticationProvider.setHideUserNotFoundExceptions(false); return daoAuthenticationProvider; }
@Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .authenticationProvider(daoAuthenticationProvider()); }
因爲之前項目中也是同樣的技術棧,並且代碼也差很少,登陸這段邏輯能夠說是徹底相同,不過以前就一直都沒有這個問題。反覆查看以後發現,在login的代碼有些不一樣框架
在ide
val authenticate = authenticationManager.authenticate(upToken);
前面還有一個ui
//執行UserDetailsService的loadUserByUsername驗證用戶名 userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
該方法會直接拋出UsernameNotFoundException,而不走spring security的AbstractUserDetailsAuthenticationProvider,也就不存在被轉換爲BadCredentialsException了。
可是這個方案有個缺點,
若是驗證用戶名經過之後,再次調用
val authenticate = authenticationManager.authenticate(upToken);
還會再執行一遍
userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
該操做是冗餘的,產生了沒必要要的數據庫查詢工做。