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

Postman调用Spring REST API异常排查及JWT授权密钥疑问

问题排查与JWT令牌使用疑问

我用Postman发起根据ID查询单个用户的GET请求,返回状态码200但无数据,数据库中明明存在对应数据。曾怀疑是antMatchers配置问题但未解决,控制台也无日志输出。其他控制器均可正常访问,仅UserController存在异常。

奇怪现象:在Authorization头中使用refreshKey替代tokenkey时请求可正常工作,请问我可以保留这种方式还是必须使用tokenkey?


SecurityConfig代码

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsServiceImpl userDetailsService;

    private JwtAuthenticationEntryPoint handler;

    public SecurityConfig(UserDetailsServiceImpl userDetailsService, JwtAuthenticationEntryPoint handler) {
        this.userDetailsService = userDetailsService;
        this.handler = handler;
    }

    /*@Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }*/

    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

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

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors()
                .and()
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(handler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/type")
                .permitAll()
                .antMatchers(HttpMethod.GET, "/nation")
                .permitAll()
                .antMatchers(HttpMethod.GET, "/recept")
                .permitAll()
                .antMatchers(HttpMethod.GET, "/recept/**")
                .permitAll()
                .antMatchers(HttpMethod.GET, "/ingredient")
                .permitAll()
                .antMatchers(HttpMethod.GET, "/recept/{\\d+}")
                .permitAll()
                .antMatchers("/users/**")
                .permitAll()
                .antMatchers("/auth/**")
                .permitAll()
                .anyRequest().authenticated();

        httpSecurity.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

UserController代码

@Slf4j
@RestController
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


    @RequestMapping(value="/users",method= RequestMethod.GET, headers = "Accept=application/json")
    public List<UserResponse> getAllUsers(){
        return userService.getAllUsers().stream().map(u -> new UserResponse(u)).collect(Collectors.toList());
    }


    @RequestMapping(value="/users",method= RequestMethod.POST, headers = "Accept=application/json")
    public ResponseEntity<Void> createUser(@RequestBody User newUser) {
        User user = userService.saveOneUser(newUser);
        if(user != null)
            return new ResponseEntity<>(HttpStatus.CREATED);
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @RequestMapping(value="/users/{userId}",method= RequestMethod.GET, headers = "Accept=application/json")
    public UserResponse getOneUser(@PathVariable Long userId) {
        log.info (String.valueOf (userId));
        User user = userService.getOneUserById(userId);
        if(user == null) {
            throw new UserNotFoundException ();
        }
        return new UserResponse(user);
    }

    @RequestMapping(value="/users/{userId}",method= RequestMethod.PUT, headers = "Accept=application/json")
    public ResponseEntity<Void> updateOneUser(@PathVariable Long userId, @RequestBody User newUser) {
        User user = userService.updateOneUser(userId, newUser);
        if(user != null)
            return new ResponseEntity<>(HttpStatus.OK);
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);

    }

    @RequestMapping(value="/users/{userId}",method= RequestMethod.DELETE, headers = "Accept=application/json")
    public void deleteOneUser(@PathVariable Long userId) {
        userService.deleteById(userId);
    }

}

UserResponse代码

@Data
public class UserResponse {

    Long id;
    int avatarId;
    String userName;

    public UserResponse(User entity) {
        this.id = entity.getId();
        this.avatarId = entity.getAvatar();
        this.userName = entity.getUser_name ();
    }
}

JwtAuthenticationEntryPoint代码

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }

}

JwtAuthenticationFilter代码

public class JwtAuthenticationFilter extends OncePerRequestFilter{

    @Autowired
    JwtTokenProvider jwtTokenProvider;

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwtToken = extractJwtFromRequest(request);
            if(StringUtils.hasText(jwtToken) && jwtTokenProvider.validateToken(jwtToken)) {
                Long id = jwtTokenProvider.getUserIdFromJwt(jwtToken);
                UserDetails user = userDetailsService.loadUserById(id);
                if(user != null) {
                    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                    auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(auth);
                }
            }
        } catch(Exception e) {
            return;
        }
        filterChain.doFilter(request, response);
    }

    private String extractJwtFromRequest(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        if(StringUtils.hasText(bearer) && bearer.startsWith("Bearer "))
            return bearer.substring("Bearer".length() + 1);
        return null;
    }

}

问题根源与解决方案

  1. 空数据返回原因
    你的JwtAuthenticationFilter中,捕获异常后直接return,没有执行filterChain.doFilter(request, response),导致请求根本没走到UserController的getOneUser方法,自然返回空内容且无日志输出。而用refreshKey时,token验证通过,过滤器正常放行,请求能到达控制器并查询到数据。

  2. refreshKey代替tokenkey的风险
    绝对不能这么做:

    • refreshToken的设计目的是获取新的AccessToken,不是用来做接口认证的,它的有效期通常更长,一旦泄露,攻击者可长期获取权限,风险极大。
    • 你的JwtTokenProvider没有区分AccessToken和RefreshToken的验证逻辑,这违反JWT最佳实践。
  3. 修复步骤

    • 修正过滤器异常处理:捕获异常后,对permitAll的接口要继续放行,不需要认证的接口不能被拦截
      try {
          // 原有逻辑
      } catch(Exception e) {
          log.error("JWT认证失败", e);
          // 仅拦截需要认证的接口
          if (!new AntPathMatcher().match("/users/**", request.getRequestURI())) {
              handler.commence(request, response, new AuthenticationException("JWT认证失败") {});
              return;
          }
      }
      filterChain.doFilter(request, response);
      
    • 区分AccessToken和RefreshToken:生成时用不同签名密钥,或在JWT的claim中标记token类型,验证时校验类型,确保RefreshToken只能用于刷新令牌
    • 检查日志配置:确保UserController的日志级别为INFO及以下,保证日志能输出到控制台
    • 调试查询逻辑:在userService.getOneUserById中添加日志,确认传入的userId和数据库查询结果是否匹配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 10:33:28