You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security注册自动登录authenticationManager返回401问题

Spring Security 注册后自动登录调用authenticationManager返回401问题排查

问题现象

  • 现有项目已完成注册、登录接口开发,登录接口运行正常,用户登录后可正常签发JWT令牌
  • 预期实现效果:用户注册成功后自动完成认证,直接返回JWT令牌,无需二次登录
  • 实际异常表现:前端提交UserDto参数后,用户数据可正常写入数据库,但认证流程阻塞,代码停在SignupController中如下位置,无自定义业务异常抛出:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
  • Postman调试接口直接返回401 Unauthorized响应

核心参考代码

SecurityConfiguration.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        http.csrf()
            .disable()
            .authorizeRequests()
            .antMatchers("/", "index", "/css/*", "/js/*")
            .permitAll()
            .antMatchers("/flightBooking", "/flightBooking/signup", "/flightBooking/login")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
}

LoginController.java

@RestController
@RequestMapping("/flightBooking/login")
public class LoginController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Autowired
    private FlightBookingUserDetailsService flightBookingUserDetailsService;
    
    @PostMapping
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthReq authReq) throws Exception {
        this.authenticate(authReq.getUsername(), authReq.getPassword());
        final UserDetails userDetails = flightBookingUserDetailsService.loadUserByUsername(authReq.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);
        return ResponseEntity.ok(new AuthRes(token));
    }
    
    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            throw new Exception("INVALID_EXCEPTION", e);
        }
    }
    
}

SignupController.java

@RestController
@RequestMapping("/flightBooking/signup")
public class SignupController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Autowired
    private FlightBookingUserDetailsService flightBookingUserDetailsService;
    
    @Autowired
    private IUserService iUserService;
    
    @Autowired
    private UserMapper mapper;
    

    @PostMapping
    public ResponseEntity<?> signUp(@RequestBody UserDto userDto) throws Exception {
        userDto.setRoleId(2L);
        Long userId = this.iUserService.saveEntity(this.mapper.map(userDto));
        if (userId > 0) {
            this.authenticate(userDto.getUsername(), userDto.getUserPass());
            final UserDetails userDetails = flightBookingUserDetailsService.loadUserByUsername(userDto.getUsername());
            final String token = jwtTokenUtil.generateToken(userDetails);
            AuthRes res = new AuthRes(token);
            res.setId(userId);
            return ResponseEntity.ok(res);
        }
        return null;
    }
    
    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            throw new Exception("INVALID_EXCEPTION", e);
        }
    }
    
}

排查思路&根因定位

按优先级从高到低排查:

  • 密码未加密存储问题(最高发)
    登录接口正常说明老用户的密码是经过BCryptPasswordEncoder加密后存储的,但注册逻辑中调用iUserService.saveEntity前,没有对前端传入的明文密码做加密处理,直接将明文存入数据库。
    调用authenticationManager.authenticate时,DaoAuthenticationProvider会用BCrypt规则比对前端传入的明文和数据库存储的密码:数据库中存储的明文不符合BCrypt哈希格式,会直接判定为凭证无效,抛出BadCredentialsException。
    当前异常捕获逻辑只抛出了自定义消息,没有打印根异常栈,加上JwtAuthenticationEntryPoint会兜底拦截所有认证相关异常返回401,就会出现无业务异常抛出、直接返回401的现象。

  • 事务未提交导致查询不到用户
    如果saveEntity方法所在的类(或者Controller层的signup方法)加了@Transactional注解,且事务传播级别为默认的REQUIRED,那么在调用saveEntity后,数据库插入操作还没有提交事务,此时authenticationManager内部调用UserDetailsService查询数据库,会读不到刚插入的用户数据,判定为用户名不存在,同样会抛BadCredentialsException返回401。

  • JWT过滤器拦截问题
    检查JwtRequestFilter代码,是否没有对/flightBooking/signup路径做放行处理,不管什么请求都强行校验请求头中的JWT:注册请求本身不会携带有效JWT,过滤器检测到无效token会直接设置未认证状态,触发entryPoint返回401,根本不会走到Controller内部的authenticate逻辑——如果已经确认用户数据成功入库,这个问题概率较低。

  • 字段映射错误
    检查UserMapper转换逻辑,是否将userDto.getUserPass()映射错了数据库字段,导致存入数据库的密码为空/错误,认证时密码比对失败。

解决方案

  1. 修复注册逻辑的密码加密问题
    注册保存用户前,注入BCryptPasswordEncoder对明文密码加密,再存入数据库:

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    
    @PostMapping
    public ResponseEntity<?> signUp(@RequestBody UserDto userDto) throws Exception {
        userDto.setRoleId(2L);
        // 加密明文密码
        userDto.setUserPass(passwordEncoder.encode(userDto.getUserPass()));
        Long userId = this.iUserService.saveEntity(this.mapper.map(userDto));
        if (userId > 0) {
            this.authenticate(userDto.getUsername(), userDto.getUserPass());
            final UserDetails userDetails = flightBookingUserDetailsService.loadUserByUsername(userDto.getUsername());
            final String token = jwtTokenUtil.generateToken(userDetails);
            AuthRes res = new AuthRes(token);
            res.setId(userId);
            return ResponseEntity.ok(res);
        }
        return null;
    }
    
  2. 修复事务问题
    不要在Controller层加事务注解,确保saveEntity方法的事务在方法返回时正常提交;如果需要在同一个事务里完成逻辑,可以在save操作后手动强制刷新ORM框架的一级缓存,或者将认证、生成token的逻辑放到事务提交完成后执行。

  3. 完善异常日志
    在catch块中打印异常的完整栈信息,不要只抛自定义消息,方便后续定位问题:

    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            // 生产环境替换为日志框架打印
            e.printStackTrace();
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            e.printStackTrace();
            throw new Exception("INVALID_EXCEPTION", e);
        }
    }
    
  4. 检查JWT过滤器逻辑
    在JwtRequestFilter中提前放行配置为permitAll的路径,不要对注册、登录接口做JWT校验:

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 放行注册、登录路径
        String path = request.getRequestURI();
        if (path.contains("/flightBooking/login") || path.contains("/flightBooking/signup")) {
            chain.doFilter(request, response);
            return;
        }
        // 原有JWT校验逻辑保持不变
    }
    

内容的提问来源于stack exchange,提问作者hossein frkh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:42:20