现有接口返回401 Unauthorized错误且服务端日志无报错如何解决
问题描述
我在现有Customer实体中新增了username、password字段,同时添加了自定义JWT过滤器、认证提供者,以及标注了@Order(2)的WebSecurityConfig配置。当我携带用户名和密码载荷发送POST请求时,接口返回401 Unauthorized错误,但服务端日志中没有任何报错信息,我已核查WebConfig文件配置无误,迟迟定位不到问题根源,以下为相关实现代码:
MyCustomerDetails(UserDetails实现类)
public class MyCustomerDetails implements UserDetails{ private static final long serialVersionUID = -5087929420394311276L; private Long id; private String username; private String password; public MyCustomerDetails() { } public MyCustomerDetails(Long id, String username, String password) { super(); this.id = id; this.username = username; this.password = password; } public static MyCustomerDetails build(Customer customer) { return new MyCustomerDetails(customer.getId(), customer.getUserName(), customer.getPassword()); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Override public String getPassword() { // TODO Auto-generated method stub return password; } @Override public String getUsername() { // TODO Auto-generated method stub return username; } ............ }
Controller类
@CrossOrigin(origins = {"http://localhost:3000"}) @RestController public class CustomerController { @Autowired CustomerAccountService customerRepo; @Autowired private CustomerJwtTokenUtil customerJwtTokenUtil; @Autowired private AuthenticationManager authenticationManager; @PostMapping(value="/validateCustomer") public ResponseEntity <?> createAuthenticationToken( @RequestBody MyCustomerDetails authenticationRequest) throws Exception { authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); // Long userId = authenticationRequest.getId(); final MyCustomerDetails userDetails = (MyCustomerDetails) customerRepo.loadUserByUsername(authenticationRequest.getUsername()); final String token = customerJwtTokenUtil.generateToken(userDetails); return new ResponseEntity<>(new JwtResponse(token), HttpStatus.OK) ; } 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_CREDENTIALS", e); } } }
WebSecurityConfig配置类
@Configuration @Order(2) @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class CustomerSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private CustomerJwtAuthenticationEntryPoint customerJwtAuthenticationEntryPoint; @Autowired private UserDetailsService myCustomerDetailsService; @Autowired private CustomerJwtRequestFilter customerJwtRequestFilter; @Bean public CustomerAccountService myCustomerAccountService() { return new CustomerAccountService(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /* * @Bean public UserDetailsService myCustomerDetailsService() { return * myCustomerDetailsService(); } */ @Bean public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService){ DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); daoAuthenticationProvider.setUserDetailsService(userDetailsService); return daoAuthenticationProvider; } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } protected void configure(AuthenticationManagerBuilder auth ) throws Exception { auth.userDetailsService(myCustomerDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .authorizeRequests() .antMatchers("/***").permitAll() // .antMatchers("/customer/**").hasAuthority("CUSTOMER") .anyRequest().authenticated() .and() .exceptionHandling() .authenticationEntryPoint(customerJwtAuthenticationEntryPoint) .and() .formLogin().permitAll() // .loginPage("/login") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login") .and() .sessionManagement() .maximumSessions(1) .and() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(customerJwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } }
CustomerAccountService服务类
@Primary public class CustomerAccountService implements UserDetailsService { @Autowired private CustomerAccountRepo custRepo; @Qualifier("passwordEncoder") @Autowired private PasswordEncoder bCryptPasswordEncoder; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Customer customer = custRepo.findByUserName(username); if(customer == null) { throw new UsernameNotFoundException("Customer not found"); } return MyCustomerDetails.build(customer); } }
项目启动类
@Configuration @EnableWebMvc //@ComponentScan(basePackages = "com.bethsaida.org.security") @EnableJpaRepositories @SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) public class BethsaidaApplication { public static void main(String[] args) {SpringApplication.run(BethsaidaApplication.class, args);} public class WebConfig implements WebMvcConfigurer { private static final long MAX_AGE_SECS = 3600; @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE") .maxAge(MAX_AGE_SECS);} } }
问题修复方案
- 启动类排除了Spring Security自动配置,首先检查
CustomerSecurityConfiguration所在包是否在启动类默认扫描路径下,若Security相关代码在com.bethsaida.org.security包下,放开启动类中注释的@ComponentScan(basePackages = "com.bethsaida.org.security")注解即可。 - 检查自定义
CustomerJwtRequestFilter的逻辑,确认登录接口/validateCustomer是否被排除了JWT校验:登录请求本身不会携带JWT,若过滤器没有对该接口放行,会直接因为缺少合法JWT返回401,在过滤器中添加路径判断逻辑,请求路径匹配登录接口时直接跳过校验即可。 - 检查
MyCustomerDetails类中省略的四个状态方法:isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpired、isEnabled,这四个方法只要有一个返回false就会直接导致认证失败,无权限访问接口,当前业务无特殊要求时四个方法全部返回true即可。 - 你同时注册了
DaoAuthenticationProviderBean,又在configure(AuthenticationManagerBuilder auth)方法中手动配置了userDetailsService和密码编码器,重复配置会导致认证逻辑冲突,删除手动配置的auth.userDetailsService(myCustomerDetailsService).passwordEncoder(passwordEncoder());代码即可,Spring会自动识别你注册的DaoAuthenticationProvider完成认证逻辑。 - 检查数据库中存储的用户密码是否是BCrypt加密后的密文:若存储的是明文密码,BCrypt密码校验会直接失败返回401,且你捕获异常后只抛出了自定义业务异常,日志级别未开启异常栈打印的情况下就不会输出报错信息,写入用户密码时必须使用你注册的
BCryptPasswordEncoder对明文密码加密后再存入数据库。
内容的提问来源于stack exchange,提问作者wizdemonizer
相关产品推荐
相关产品推荐

