Spring Security环境下静态资源加载失败(401未授权)问题求助
问题描述
应用采用自定义认证机制:
/api/**端点使用Token认证/manager/**和/viewer/**使用表单登录- API接口用
@RestController,网页用@Controller
访问页面时出现以下问题:
- 静态资源无法加载,请求返回
net::ERR_ABORTED 401 (Unauthorized) /viewer/home请求返回401,但能正常渲染HTML内容并加载DTO数据- 直接在浏览器访问
http://localhost:8080/css/operator.css可以正常获取CSS文件
控制器代码
@GetMapping("/viewer/home") public String operatorHome(Model model) { // logic... model.addAttribute("files", dtos); return "operator-home"; }
安全配置代码
@Configuration @EnableWebSecurity public class WebSecurityConfig { @Autowired private AuthenticationService authService; @Bean @Order(1) public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/manager/**").hasAuthority(Role.MANAGER.name()) .requestMatchers("/viewer/**").hasAuthority(Role.OPERATOR.name()) .requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**").permitAll() .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() .anyRequest().permitAll() ) .formLogin((form) -> form.successHandler(new CustomAuthenticationSuccessHandler())) .logout(LogoutConfigurer::permitAll) .httpBasic(Customizer.withDefaults()); return http.build(); } @Bean @Order(2) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/api/**").hasAuthority(Role.USER.name()) .anyRequest().permitAll() ) .addFilterBefore(new TokenAuthenticationFilter(authService), UsernamePasswordAuthenticationFilter.class) .sessionManagement((session) -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .httpBasic(Customizer.withDefaults()) ; return http.build(); } @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() { return new TokenAuthenticationFilter(authService); } @Bean CustomUserDetailsService customUserDetailsService() { return new CustomUserDetailsService(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); } }
补充:即使注释掉第二个API过滤器链,保留
anyRequest().permitAll(),问题依然存在;Postman测试REST API正常。
HTML代码
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <!-- 这个加载正常 --> <link rel="stylesheet" type="text/css" th:href="@{/css/operator.css}"> <!-- 这个无法加载 -->
请求日志
DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Securing GET /viewer/home DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=test_operator, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[OPERATOR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=9E3858A4DC3CD548374F4874C21539D6], Granted Authorities=[OPERATOR]]] DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Secured GET /viewer/home DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/viewer/home", parameters={} DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.lavkatech.audiorecognition.controller.WebController#operatorHome(Model) DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] org.hibernate.SQL : select ao1_0.id,ao1_0.audio_len,ao1_0.checked_by_id,ao1_0.checked_on,ao1_0.file_loc,ao1_0.file_name,ao1_0.file_size,ao1_0.file_text,ao1_0.is_checked,ao1_0.op_end_time,ao1_0.op_start_time,ao1_0.requested_by_value from files ao1_0 DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8, application/signed-exchange;v=b3;q=0.7] DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed 401 UNAUTHORIZED DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] o.s.security.web.FilterChainProxy : Securing GET /css/operator.css DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] o.s.security.web.FilterChainProxy : Secured GET /css/operator.css DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : GET "/css/operator.css", parameters={} DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]] DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 401 UNAUTHORIZED DEBUG 13628 --- [XXXXXXXXXXXX] [nio-8080-exec-4] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=test_operator, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[OPERATOR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=9E3858A4DC3CD548374F4874C21539D6], Granted Authorities=[OPERATOR]]]
问题分析与解决方案
从日志可见,/viewer/home和静态资源请求已被Spring Security放行,但DispatcherServlet最终返回401,说明问题出在视图渲染或资源处理阶段的额外拦截,而非Spring Security过滤器链本身。
排查与修复步骤:
移除webFilterChain中的httpBasic配置
表单登录场景不需要HTTP Basic认证,两个过滤器链同时配置httpBasic可能导致认证逻辑冲突。修改webFilterChain:// 移除该行 .httpBasic(Customizer.withDefaults());检查自定义拦截器/过滤器
排查是否存在WebMvcConfigurer中添加的自定义拦截器,确保静态资源和页面请求不会被额外拦截。若有不必要的拦截规则,直接移除或调整匹配路径。验证模板引擎权限配置
检查Thymeleaf模板中是否使用了sec:authorize标签,若权限表达式与当前用户角色不匹配,会导致视图渲染后返回401。移除或修正相关标签。显式配置静态资源映射
确保静态资源路径映射正确,添加WebMvc配置类:@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/css/**") .addResourceLocations("classpath:/static/css/"); registry.addResourceHandler("/js/**") .addResourceLocations("classpath:/static/js/"); } }检查认证成功处理器
排查CustomAuthenticationSuccessHandler是否正确设置会话属性,避免跳转时修改认证状态导致后续请求失效。
内容的提问来源于stack exchange,提问作者uauauaua
相关产品推荐
相关产品推荐

