我有一个使用 Spring Boot 制作的 Rest API,并由外部服务授权服务器使用 OAUTH2 进行保护,并且我正在使用 springdoc-openapi 创建文档。
问题是,当向 /token 端点发送 post 请求时,授权服务器不会返回带有 accessToken 的响应,而是抛出错误。
授权服务器:安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final String[] WHITELIST = {"/h2-console/**", "/css/**", "/images/**", "/js/**", "/actuator/health"};
@Bean
@Order(1)
public SecurityFilterChain authFilterChain(HttpSecurity httpSecurity) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(httpSecurity);
httpSecurity.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
httpSecurity
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.exceptionHandling(exceptions -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
).oauth2ResourceServer( rs -> rs.jwt(Customizer.withDefaults()));
return httpSecurity.build();
}
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf -> csrf.disable()) //disable csrf
.headers( headers -> headers.frameOptions(fo -> fo.sameOrigin())) //allowing h2 to be displayed as a frame
.authorizeHttpRequests(authz -> authz
.requestMatchers( WHITELIST).permitAll()
.requestMatchers(HttpMethod.POST, "/register").permitAll()
.requestMatchers(HttpMethod.GET, "/login").permitAll()
.requestMatchers(HttpMethod.OPTIONS,"/oauth2/token").permitAll()
.requestMatchers(HttpMethod.POST,"/oauth2/token").permitAll()
.anyRequest().authenticated())
.formLogin(formLogin ->
formLogin.loginPage("/login"))
.logout( logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")))
.build();
}
...
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*", "http://172.28.48.1:8083"));
configuration.addAllowedMethod(HttpMethod.POST);
configuration.addAllowedMethod(HttpMethod.OPTIONS);
configuration.addAllowedHeader("Authorization");
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
账户服务:带有安全方案的 Swagger 配置
@Configuration
public class SwaggerConfig {
@Autowired
private OAuth2Config oAuth2Config;
private String getAuthorizationUri() {
return oAuth2Config.findAuthServiceUris().get("authorizationUri");
}
private String getTokenUri() {
return oAuth2Config.findAuthServiceUris().get("tokenUri");
}
private Components apiComponents() {
Components components = new Components();
components.addSecuritySchemes("microservices_oauth2", microservicesOauth2());
return components;
}
private SecurityScheme microservicesOauth2() {
SecurityScheme securityScheme = new SecurityScheme();
securityScheme.type(SecurityScheme.Type.OAUTH2);
securityScheme.flows(new OAuthFlows().clientCredentials(new OAuthFlow().authorizationUrl(this.getAuthorizationUri()).tokenUrl(this.getTokenUri())));
securityScheme.description("OAuth2 Client Credentials Flow for Microservices");
return securityScheme;
}
private List<SecurityRequirement> securityRequirements() {
SecurityRequirement securityRequirement = new SecurityRequirement();
securityRequirement.addList("microservices_oauth2");
return List.of(securityRequirement);
}
@Bean
public OpenAPI openAPI() {
return new OpenAPI().info(
new Info().title("Account Docs")
.license(new License().name("The MIT License (MIT)").url("https://opensource.org/license/mit"))
.version("0.1.0")
.contact(new Contact().name("Durian Sosa").email("[email protected]").url("https://dmsosa.github.io/dmblog/"))
.description("Account API for Microservices Architecture")
)
.components(apiComponents())
.security(securityRequirements());
}
}
这似乎是一个 CORS 问题,但我已在授权服务器的第一个过滤器中添加了 cors 配置。也许我不完全理解这两个 SecurityFilterChains 如何协同工作?希望有任何提示
预期结果:OAuth2 服务器在收到正确的客户端凭据后返回带有访问令牌的响应
在发送请求时 OAuth2Server 的日志中,显示了这一点
\ Invoking DisableEncodeUrlFilter (1/11)
2024-07-08T16:41:44.092+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking LogoutFilter (5/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.s.w.a.logout.LogoutFilter : Did not match request to Ant [pattern='/logout', GET]
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking UsernamePasswordAuthenticationFilter (6/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (7/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.s.w.s.HttpSessionRequestCache : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (8/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (9/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (10/11)
2024-07-08T16:41:44.093+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Invoking AuthorizationFilter (11/11)
2024-07-08T16:41:44.094+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] estMatcherDelegatingAuthorizationManager : Authorizing SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@51f918f2]
2024-07-08T16:41:44.095+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] estMatcherDelegatingAuthorizationManager : Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@51f918f2] using org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer$$Lambda/0x00000294d6ba32d8@1e5ae884
2024-07-08T16:41:44.095+01:00 DEBUG 19404 --- [auth-service] [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Secured OPTIONS /oauth2/token
2024-07-08T16:41:44.097+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
2024-07-08T16:41:44.098+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2024-07-08T16:41:44.098+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-08T16:41:44.098+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-08T16:41:44.098+01:00 TRACE 19404 --- [auth-service] [nio-9000-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=172.28.48.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
将“*”添加到允许的标头,将“localhost”添加到允许的来源并向两个SecurityFilterChains添加cors配置后,授权按预期工作。
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf -> csrf.disable()) //disable csrf
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*", "http://172.28.48.1:8083", "http://localhost:8083"));
configuration.addAllowedMethod(HttpMethod.POST);
configuration.addAllowedMethod(HttpMethod.OPTIONS);
configuration.addAllowedHeader("Authorization");
configuration.addAllowedHeader("*");
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
这解决了问题,但我不完全明白为什么,想知道......