Spring Boot SecurityFilterChain注解致Postman与Swagger权限互斥问题
Spring Boot Azure AD权限控制异常:Postman与Swagger访问权限互斥问题
问题现象
- Postman和Swagger均通过Azure AD正确获取令牌后,仅Postman能访问控制器接口,Swagger返回401未授权
- 当
MySecurityService类添加@Component/@Service/@Configuration注解时:- Postman请求控制器正常通过
- Swagger请求返回HTTP 401,响应头包含
www-authenticate: Bearer
- 移除上述注解后,访问权限反转:Swagger可正常访问控制器,Postman无法访问
相关代码
MySecurityService类
package br.com.xxxx.authentication.service; import br.com.xxxx.authentication.service.abstraction.interfaces.IMySecurityService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.web.SecurityFilterChain; @Configuration public class MySecurityService implements IMySecurityService { @Order(Ordered.HIGHEST_PRECEDENCE) @Bean public SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception { http.oauth2Login(); final var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository); logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}"); http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler)); http.cors().disable(); http.csrf().disable(); http.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); http.authorizeHttpRequests().requestMatchers("/swagger*/**", "/v3/api-docs*/**", "/login/**", "/oauth2/**", "/user/login/**").permitAll().anyRequest().authenticated(); return http.build(); } }
IMySecurityService接口
package br.com.xxxx.authentication.service.abstraction.interfaces; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.web.SecurityFilterChain; public interface IMySecurityService { SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception; }
调试日志及请求信息
当使用@Configuration注解时,Swagger返回401;移除注解后权限反转。以下是应用启动及请求日志:
Connected to the target VM, address: '127.0.0.1:57781', transport: 'socket' . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.1.1) 2024-01-26T12:56:48.083-03:00 INFO 74604 --- [ main] b.com.xxxx.PlatformMstemplateApplication : Starting PlatformMstemplateApplication using Java 18.0.2.1 with PID 74604 (C:\Reconstruindo\Platform.MsTemplate.Java\target\classes started by 4960000083 in C:\Reconstruindo\Platform.MsTemplate.Java) 2024-01-26T12:56:48.089-03:00 INFO 74604 --- [ main] b.com.xxxx.PlatformMstemplateApplication : No active profile set, falling back to 1 default profile: "default" 2024-01-26T12:56:50.445-03:00 INFO 74604 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=e197ce10-f929-386d-a01e-9b5a9061e13f 2024-01-26T12:56:51.076-03:00 INFO 74604 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2024-01-26T12:56:51.087-03:00 INFO 74604 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-01-26T12:56:51.088-03:00 INFO 74604 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10] 2024-01-26T12:56:51.287-03:00 INFO 74604 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-01-26T12:56:51.290-03:00 INFO 74604 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3054 ms 2024-01-26T12:57:05.845-03:00 ERROR 74604 --- [nio-8080-exec-8] Retira.MsTemplate : executando controller {"@timestamp":"2024-01-26T12:57:05.8453726-03:00","@version":"1","message":"executando controller","logger_name":"Retira.MsTemplate","thread_name":"http-nio-8080-exec-8","level":"ERROR","level_value":40000,"CONSOLE_LOG_THRESHOLD":"TRACE","FILE_LOG_PATTERN":"%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %5p 74604 --- [%t] %-40.40logger{39} : %m%n%wEx","FILE_LOG_CHARSET":"UTF-8","PID":"74604","CONSOLE_LOG_PATTERN":"%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){faint} %clr(%5p) %clr(74604){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx","FILE_LOG_THRESHOLD":"TRACE","CONSOLE_LOG_CHARSET":"UTF-8"} 2024-01-26T12:57:05.853-03:00 ERROR 74604 --- [nio-8080-exec-8] Retira.MsTemplate : executando repositotory {"@timestamp":"2024-01-26T12:57:05.8533763-03:00","@version":"1","message":"executando repositotory","logger_name":"Retira.MsTemplate","thread_name":"http-nio-8080-exec-8","level":"ERROR","level_value":40000,"CONSOLE_LOG_THRESHOLD":"TRACE","FILE_LOG_PATTERN":"%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %5p 74604 --- [%t] %-40.40logger{39} : %m%n%wEx","FILE_LOG_CHARSET":"UTF-8","PID":"74604","CONSOLE_LOG_PATTERN":"%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){faint} %clr(%5p) %clr(74604){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx","FILE_LOG_THRESHOLD":"TRACE","CONSOLE_LOG_CHARSET":"UTF-8"} 2024-01-26T12:57:06.533-03:00 ERROR 74604 --- [nio-8080-exec-8] Retira.MsTemplate : finalizando repositotory {"@timestamp":"2024-01-26T12:57:06.5333754-03:00","@version":"1","message":"finalizando repositotory","logger_name":"Retira.MsTemplate","thread_name":"http-nio-8080-exec-8","level":"ERROR","level_value":40000,"CONSOLE_LOG_THRESHOLD":"TRACE","FILE_LOG_PATTERN":"%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %5p 74604 --- [%t] %-40.40logger{39} : %m%n%wEx","FILE_LOG_CHARSET":"UTF-8","PID":"74604","CONSOLE_LOG_PATTERN":"%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){faint} %clr(%5p) %clr(74604){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx","FILE_LOG_THRESHOLD":"TRACE","CONSOLE_LOG_CHARSET":"UTF-8"}
问题分析与解决方案
核心原因
- SecurityFilterChain注册冲突:带
@Configuration时,自定义的SecurityFilterChain生效,Swagger请求虽被配置为permitAll,但Swagger UI默认通过会话传递令牌,而Resource Server仅识别请求头中的Bearer令牌,导致验证失败。 - 无注解时的默认配置:移除注解后,自定义配置不加载,Spring Boot启用默认安全规则,允许会话认证,Swagger的会话令牌可通过,但Postman的请求头令牌无法被默认规则识别,导致访问失败。
解决步骤
1. 修复SecurityFilterChain配置,兼容两种认证方式
保留@Configuration注解,调整配置同时支持请求头令牌(Postman)和会话认证(Swagger),并确保Swagger全路径放行:
@Configuration public class MySecurityService implements IMySecurityService { @Order(Ordered.HIGHEST_PRECEDENCE) @Bean public SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception { // 配置OAuth2登录,支持Swagger UI的会话认证 http.oauth2Login(oauth2 -> oauth2.loginPage("/oauth2/authorization/azure-ad")); // 配置OIDC登出逻辑 final var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository); logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}"); http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler)); // 放行所有Swagger相关路径,包括UI、文档和OAuth2回调 http.authorizeHttpRequests(auth -> auth .requestMatchers( "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/v3/api-docs.yaml", "/login/**", "/oauth2/**", "/user/login/**" ).permitAll() .anyRequest().authenticated()); // 配置Resource Server支持JWT,兼容Postman的请求头令牌 http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); // 按需配置CORS/CSRF:保留CORS支持,忽略Swagger路径的CSRF校验 http.cors(Customizer.withDefaults()); http.csrf(csrf -> csrf.ignoringRequestMatchers("/v3/api-docs/**", "/swagger-ui/**")); return http.build(); } }
2. 配置Swagger自动携带Bearer令牌
确保OpenAPI配置中启用Azure AD OAuth2认证,让Swagger UI登录后自动在请求头中添加令牌:
@Configuration public class OpenApiConfig { @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .components(new Components() .addSecuritySchemes("azure-ad", new SecurityScheme() .type(SecurityScheme.Type.OAUTH2) .flows(new OAuthFlows() .authorizationCode(new OAuthFlow() .authorizationUrl("https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/authorize") .tokenUrl("https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/token") .scopes(new Scopes() .addString("api://{你的客户端ID}/access_as_user", "以用户身份访问API")))))) .addSecurityItem(new SecurityRequirement().addList("azure-ad")); } }
3. 验证令牌传递
- Postman:确认请求头包含
Authorization: Bearer {你的令牌} - Swagger UI:登录后通过浏览器开发者工具查看网络请求,确认请求头自动携带Bearer令牌
验证结果
调整配置后,Postman通过请求头令牌、Swagger通过OAuth2会话令牌均可正常访问控制器接口,权限互斥问题解决。
内容的提问来源于stack exchange,提问作者Carlos Henrique
相关产品推荐
相关产品推荐

