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

Spring Boot Security部署WAR后API请求返回401认证错误(凭证合法)

解决Spring Boot WAR部署后JWT认证失效的问题

嘿,我来帮你分析下这个头疼的问题——嵌入式Tomcat里好好的JWT认证,打包成WAR部署到外部容器就全失效了。先理清楚关键信息,再逐一排查可能的原因:

问题背景梳理

  • 技术栈:Spring Boot + React 单页应用,JWT认证机制
  • 开发环境:用嵌入式Tomcat时,所有API接口(包括需要认证的/api/user/me)都能正常访问
  • 部署异常:打包为WAR后部署到Wildfly、Glassfish 4.1.2、Tomcat 8.5.23,均出现已认证请求被拒绝的情况
  • 关键现象:
    • JwtFilter监控显示凭证本身没有错误,但Tomcat日志抛出JwtAuthenticationEntryPoint : Responding with unauthorized error. Message - Full authentication is required to access this resource
    • 请求地址变化:开发时是http://localhost:8080/api/user/me,部署后多了应用上下文路径myproject,变成http://localhost:8080/myproject/api/user/me

你的核心代码参考

Spring Boot主类

@SpringBootApplication 
@EntityScan(basePackageClasses = { AccountingApplication.class, Jsr310JpaConverters.class }) 
public class AccountingApplication extends SpringBootServletInitializer{ 
    @PostConstruct 
    void init() { 
        TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 
    } 
    public static void main(String[] args) { 
        SpringApplication.run(applicationClass, args); 
    } 
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 
        return application.sources(applicationClass); 
    } 
    private static Class<AccountingApplication> applicationClass = AccountingApplication.class; 
}

安全配置类

@Configuration 
@EnableWebSecurity 
@EnableGlobalMethodSecurity( 
    securedEnabled = true, 
    jsr250Enabled = true, 
    prePostEnabled = true 
) 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    @Autowired CustomUserDetailsService userDetailsService; 
    @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; 
    @Bean 
    public JwtAuthenticationFilter jwtAuthenticationFilter() { 
        return new JwtAuthenticationFilter(); 
    } 
    @Override 
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { 
        authenticationManagerBuilder. 
            userDetailsService(userDetailsService). 
            passwordEncoder(passwordEncoder()); 
    } 
    @Bean(BeanIds.AUTHENTICATION_MANAGER) 
    @Override 
    public AuthenticationManager authenticationManagerBean() throws Exception { 
        return super.authenticationManagerBean(); 
    } 
    @Bean 
    public PasswordEncoder passwordEncoder() { 
        /* removed for clarity */ 
    } 
    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
        http 
            .cors() 
            .and() 
            .csrf() 
            .disable() 
            .exceptionHandling() 
            .authenticationEntryPoint(unauthorizedHandler) 
            .and() 
            .sessionManagement() 
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
            .and() 
            .authorizeRequests() 
            .antMatchers("/resources/**") 
            .permitAll() 
            .antMatchers("/", "/favicon.ico", "/**/*.png", "/**/*.gif", "/**/*.svg", "/**/*.woff", "/**/*.woff2", "/**/*.ttf", "/**/*.eot", "/**/*.jpg", "/**/*.html", "/**/*.css", "/**/*.js") 
            .permitAll() 
            .antMatchers("/api/auth/**") 
            .permitAll() 
            .anyRequest() 
            .authenticated(); 
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); 
    }
}

可能的原因及解决方案

1. 应用上下文路径导致JWT Filter未正确拦截请求

这是最常见的原因!部署到外部容器后,应用多了myproject上下文路径,而你的JwtAuthenticationFilter可能是基于完整请求URI判断是否处理认证,而不是Servlet路径。

比如Filter里如果写了:

if (request.getRequestURI().startsWith("/api/")) {
    // 处理JWT认证
}

部署后请求URI是/myproject/api/user/me,startsWith("/api/")会返回false,Filter直接跳过认证逻辑,导致请求被Security拦截。

修复方案:
在Filter中获取Servlet路径(自动去掉上下文路径)来判断:

// 在JwtAuthenticationFilter的doFilterInternal方法中
String servletPath = request.getServletPath();
if (servletPath.startsWith("/api/")) {
    // 提取Token、验证Token、设置Authentication等逻辑
}

或者用上下文路径截取后判断:

String contextPath = request.getContextPath();
String requestUri = request.getRequestURI().substring(contextPath.length());
if (requestUri.startsWith("/api/")) {
    // 处理认证
}

2. CORS配置未适配上下文路径

部署后请求路径带了上下文,CORS配置可能没有正确覆盖这个路径,导致前端请求的OPTIONS预检失败,或者Token没有被正确携带。

修复方案:
自定义全局CORS配置,确保匹配所有路径:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(Collections.singletonList("*")); // 生产环境请替换为具体域名
    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config); // 匹配上下文路径后的所有路径
    return source;
}

然后在SecurityConfig中使用这个配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().configurationSource(corsConfigurationSource()) // 替换原来的.cors()
        .and()
        // 其他配置...
}

3. JWT Token的Audience/Issuer与部署环境不匹配

如果你的JWT生成时包含了aud(受众)或iss(签发者)字段,开发环境是http://localhost:8080,部署后变成http://localhost:8080/myproject,验证Token时会因为不匹配而失败。

修复方案:

  • 生成Token时,将aud/iss设置为包含上下文路径的地址,或者动态获取当前应用的上下文路径
  • 测试环境可以临时关闭aud/iss验证(不建议生产环境这么做)

4. 外部容器的依赖冲突或版本差异

嵌入式Tomcat和外部容器的版本差异可能导致Filter执行顺序、Servlet API版本不兼容等问题。

修复方案:
确保Maven中排除嵌入式Tomcat依赖,并引入Servlet API的provided依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>

5. 增加日志排查细节

在JwtAuthenticationFilter中添加详细日志,明确知道Filter是否处理了请求、Token是否被正确提取:

private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    logger.info("========== Processing Request ==========");
    logger.info("Full Request URI: {}", request.getRequestURI());
    logger.info("Context Path: {}", request.getContextPath());
    logger.info("Servlet Path: {}", request.getServletPath());
    
    String jwt = getJwtFromRequest(request);
    logger.info("Extracted JWT Token: {}", jwt != null ? "Found Token" : "Token Missing");
    
    // 后续认证逻辑...
}

通过日志可以快速定位Filter是否生效,以及Token提取是否正常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:46:29