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

Spring Boot集成JWT,Postman请求/authenticate报401 Unauthorized错误

Spring Boot JWT认证接口返回401 Unauthorized问题排查

问题描述

在Spring Boot中实现JWT令牌认证,测试/authenticate接口时,向http://localhost:8080/authenticate发送POST请求(请求体为Raw+JSON格式):

{
"username": "admin123",
"password": "admin@pass"
}

已确认该用户名和密码存在于MySQL数据库中,但请求始终返回401 Unauthorized错误。

相关代码

WebSecurityConfiguration

@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired
private JwtRequestFilter jwtRequestFilter;

@Autowired
private UserDetailsService jwtService;

@Autowired
public void setJwtAuthenticationEntryPoint(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) {
    this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
}

@Autowired
public void setJwtRequestFilter(JwtRequestFilter jwtRequestFilter) {
    this.jwtRequestFilter = jwtRequestFilter;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
}

@Bean
public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.cors();
    httpSecurity.csrf().disable()
            .authorizeHttpRequests()
                .requestMatchers("/authenticate").permitAll()
                .requestMatchers(HttpHeaders.ALLOW).permitAll()
                .anyRequest().authenticated()
            .and()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    return httpSecurity.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder.userDetailsService(jwtService).passwordEncoder(passwordEncoder());
}

JwtRequest

public class JwtRequest {

    private String username;
    private String password;

    public JwtRequest(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

JwtService

@Service
public class JwtService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private AuthenticationManager authenticationManager;

    public JwtResponse createJwtToken(JwtRequest jwtRequest) throws Exception {
        String username = jwtRequest.getUsername();
        String password = jwtRequest.getPassword();
        authenticate(username, password);

        final UserDetails userDetails = loadUserByUsername(username);
        String newGeneratedToken = jwtUtil.generateToken(userDetails);

        User user = userRepository.findById(username).get();

        return new JwtResponse(user, newGeneratedToken);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findById(username).get();

        if (user != null) {
            return new org.springframework.security.core.userdetails.User(
                    user.getUserName(), 
                    user.getUserPassword(), 
                    getAuthorities(user)
            );
        } else {
            throw new UsernameNotFoundException("Username is not valid");
        }
    }

    private Set getAuthorities(User user) {
        Set authorities = new HashSet();

        user.getRole().forEach(role -> {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
        });
        return authorities;
    }

    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new Exception("User is disabled");
        } catch (BadCredentialsException e) {
            throw new Exception("Bad credentials for user");
        }
    }
}

JwtController

@RestController
@CrossOrigin
public class JwtController {

    @Autowired
    private JwtService jwtService;

    @PostMapping({"/authenticate"})
    public JwtResponse createJwtToken(@RequestBody JwtRequest jwtRequest) throws Exception {
        return jwtService.createJwtToken(jwtRequest);
    }
}

排查方向与解决办法

1. 密码编码不匹配(最常见原因)

配置中使用BCryptPasswordEncoder作为密码编码器,必须确保数据库中存储的密码是经过该编码器加密后的字符串。如果创建用户时直接存储明文密码,或者使用了其他加密方式,会导致BadCredentialsException,最终返回401。

解决:创建用户时执行以下逻辑加密密码:

// 注入PasswordEncoder
@Autowired
private PasswordEncoder passwordEncoder;

// 创建用户时加密密码
User user = new User();
user.setUserName("admin123");
user.setUserPassword(passwordEncoder.encode("admin@pass")); // 关键:用编码器加密
userRepository.save(user);

2. User实体字段匹配问题

在JwtService.loadUserByUsername方法中,使用user.getUserName()获取用户名、user.getUserPassword()获取密码,需确认:

  • JwtRequest中的username字段对应User实体的userName属性(数据库字段需一致)
  • 数据库中存储的用户名确实是admin123,没有大小写或拼写错误

3. AuthenticationManager配置冲突

Spring Security 5.7+推荐使用SecurityFilterChain替代旧版的configureGlobal配置,两者同时存在可能导致配置不生效。

解决:删除configureGlobal方法,在SecurityFilterChain中直接配置用户详情服务和密码编码器:

@Bean
public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.cors();
    httpSecurity.csrf().disable()
            .authorizeHttpRequests()
                .requestMatchers("/authenticate").permitAll()
                .requestMatchers(HttpHeaders.ALLOW).permitAll()
                .anyRequest().authenticated()
            .and()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            // 添加以下配置
            .userDetailsService(jwtService)
            .passwordEncoder(passwordEncoder());

    httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    return httpSecurity.build();
}

4. JwtRequestFilter拦截了放行请求

检查JwtRequestFilter.doFilterInternal方法,确保不会拦截/authenticate接口:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    String requestURI = request.getRequestURI();
    // 如果是认证接口,直接放行
    if ("/authenticate".equals(requestURI)) {
        chain.doFilter(request, response);
        return;
    }
    // 其他JWT验证逻辑...
}

5. 开启调试日志定位问题

添加Spring Security调试日志,查看认证过程的详细错误信息:
在application.properties中添加:

logging.level.org.springframework.security=DEBUG

启动应用后重新测试,查看控制台日志,会输出认证失败的具体原因(如密码不匹配、用户不存在等)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:32:00