Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 636|回复: 0

Spring-Security无法正常捕捉到UsernameNotFoundException异常

[复制链接]
  • TA的每日心情
    奋斗
    昨天 22:25
  • 签到天数: 790 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722766
    发表于 2021-5-2 13:24:24 | 显示全部楼层 |阅读模式

    前言

    在Web应用开发中,安全一直是非常重要的一个方面。在庞大的spring生态圈中,权限校验框架也是非常完善的。其中,spring security是非常好用的。今天记录一下在开发中遇到的一个spring-security相关的问题。

    问题描述

    使用spring security进行授权登录的时候,发现登录接口无法正常捕捉UsernameNotFoundException异常,捕捉到的一直是BadCredentialsException异常。我们的预期是:

    • UsernameNotFoundException -> 用户名错误
    • BadCredentialsException -> 密码错误

    贴几个比较重要的代码:

    1. 登录业务逻辑

    @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());
        }
    }
    

    2. spring security 的UserDetailsService 实现类

    @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);
            }
        }
    }
    

    3. 登录接口

    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);
    }
    

    实际上,不管是抛出什么错,最后抓到的都是BadCredentialsException

    问题定位

    debug大法

    断点

    跟踪

    经过步进法跟踪代码,发现问题所在,位于

    AbstractUserDetailsAuthenticationProvider
    public Authentication authenticate(Authentication authentication)
    

    结论

    1. loadUserByUsername方法确实抛出了UsernameNotFoundException
    2. 走到AbstractUserDetailsAuthenticationProvider的authenticate方法的时候,如果hideUserNotFoundExceptions = true,直接就覆盖了UsernameNotFoundException异常并抛出BadCredentialsException异常,这也就解释了,为什么总是捕捉到BadCredentialsException异常

    问题解决

    既然已经找到了是因为hideUserNotFoundExceptions = true导致的问题,那把hideUserNotFoundExceptions = false不就完事了吗?

    方案1

    参考stackoverflow大神回答

    修改WebSecurityConfig配置,添加AuthenticationProvider Bean

    @Bean
    public AuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        return daoAuthenticationProvider;
    }
    

    配置AuthenticationProvider Bean

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .authenticationProvider(daoAuthenticationProvider());
    }
    

    方案2

    由于以前项目中也是一样的技术栈,而且代码也差不多,登录这段逻辑可以说是完全相同,不过之前就一直都没有这个问题。反复查看之后发现,在login的代码有些不同

    val authenticate = authenticationManager.authenticate(upToken);
    

    前面还有一个

    //执行UserDetailsService的loadUserByUsername验证用户名
    userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
    

    该方法会直接抛出UsernameNotFoundException,而不走spring security的AbstractUserDetailsAuthenticationProvider,也就不存在被转换为BadCredentialsException了。

    但是这个方案有个缺点,

    如果验证用户名通过以后,再次调用

    val authenticate = authenticationManager.authenticate(upToken);
    

    还会再执行一遍

    userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
    

    该操作是冗余的,产生了不必要的数据库查询工作。

    推荐使用方案1

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-11 12:41 , Processed in 1.116499 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表