首先,在不为security加入自定义过滤器的前提下,只能给出:用户名或密码错误
的提示,没有办法细化到:用户名错了
或是 密码错了
的提示。
其次,如果只是想使loadUserByUsername
返回类似用户名或密码错误
的提示,我认同楼上的方法,直接抛出异常即可:
User userInfo = userService.findUserByUserName(username);
if(userInfo == null){
throw new UsernameNotFoundException();
}
此时,前台将得到了一个401错误。表示:用户名或密码错误。
我简单说下为什么返回null会发生500,而抛出UsernameNotFoundException
则会返回401.
在AbstractUserDetailsAuthenticationProvider
中有这么几行核心代码:
try {
// 获取当前登录用户,该方法将调用我们自定义的loadUserByUsername方法。
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
if (hideUserNotFoundExceptions) {
// 由于hideUserNotFoundExceptions的默认值为true
// 所以若接收到UsernameNotFoundException则抛出该异常,则抛出BadCredentialsException的异常。
// 这正是会提示401的原因
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
然后另一个核心文件:DaoAuthenticationProvider
中有这么几行核心代码:
try {
// 调用我们的loadUserByUsername方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
// 这是关键,如果loadUserByUsername方法返回null,则将抛出InternalAuthenticationServiceException异常
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
// 该异常直接向上抛出InternalAuthenticationServiceException
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
最后,在BasicAuthenticationFilter
又有这么几行代码:
try {
// 省略部分代码
if (authenticationIsRequired(username)) {
// 以下语句完成认证:
// 1. 未发生异常,说明认证成功。继续向下执行
// 2. 发生BadCredentialsException异常,不能够catch接收。异常向上抛出,最终被Sring获取,返回401
// 3. 发生InternalAuthenticationServiceException,该异常继承于AuthenticationException。将被catch接收。
// catch中清空了用户登录信息后直接返回,(猜想,未跑demo)近而在后续的语句执行中发生了500异常。
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
// 未发生异常,用户名密码认证成功
}
}
catch (AuthenticationException failed) {
// 清空登录信息后直接return
return;
}
最后再说一下为什么我们无法分辨出是:用户名不存在或密码错误。
在DaoAuthenticationProvider
有以下几行是验证密码是否正确的:
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
如上所示:如果密码未匹配成功,同样会抛出BadCredentialsException异常。既然用户名不存在与密码错误同样抛出的是BadCredentialsException异常,而Spring Security处理BadCredentialsException的方法又是统一的,所以得到401实际上只能代表接收到了BadCredentialsException异常,而该异常是由用户名错误引起的,或是由密码错误引起的,就无从得知了。
总结:Spring Security是个相对复杂的工程,水平有限,相信也没能讲明白,取其精华去其糟粕吧。
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…