I’ve created a Spring OAuth Client (that acts as the BFF) between a reverse proxy, and Angular, an Auth Server, and a Resource Server.
However, when I access the end-point
http://localhost:9090/oauth2/authorization/in-house-auth-server
(my BFF sever is directly on port http://localhost:9090, so this is not even going via the reverse proxy)
I’d expect this to just redirect to the auth server to be authorized
http://localhost:6060/oauth2/authorize
But it keeps saying 404 Page Not Found, and I cannot work out where I’m going wrong?
Here is my gitHub repo for all the code (refer to the BFF folder)
https://github.com/dreamstar-enterprises/docs/tree/master/Spring%20BFF/bff
Can someone help?
The security chain in the BFF currently looks like this:
@Order(Ordered.LOWEST_PRECEDENCE - 1)
internal class BffSecurityConfig () {
private lateinit var reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository
private lateinit var reactiveAuthorizedClientRepository: ServerOAuth2AuthorizedClientRepository
private lateinit var reactiveAuthorizedClientService: ReactiveOAuth2AuthorizedClientService
@Value("${reverse-proxy-uri}")
private lateinit var reverseProxyUri: String
private lateinit var bffUri: String
fun clientSecurityFilterChain(
http: ServerHttpSecurity,
serverCsrfTokenRepository: ServerCsrfTokenRepository,
spaCsrfTokenRequestHandler: SPACsrfTokenRequestHandler,
csrfCookieFilter: CsrfCookieFilter,
requestCache: RequestCache,
// sessionRegistry: SpringSessionBackedReactiveSessionRegistry<ReactiveRedisIndexedSessionRepository.RedisSession>,
// maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler,
oauthAuthorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver,
loginSuccessHandler: LoginSuccessHandler,
loginFailureHandler: LoginFailureHandler,
): SecurityWebFilterChain {
csrf.csrfTokenRepository(serverCsrfTokenRepository)
csrf.csrfTokenRequestHandler(spaCsrfTokenRequestHandler)
cors.configurationSource {
CorsConfiguration().apply {
// ensure this matches the Angular app URL
allowedOrigins = listOf(reverseProxyUri)
allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
allowedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
exposedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
// required if credentials (cookies, authorization headers) are involved
// configure request cache
// .requestCache { cache ->
// cache.requestCache(requestCache)
// .sessionManagement {sessionManagement ->
// sessionManagement.concurrentSessions { sessionConcurrency ->
// .maximumSessions(SessionLimit.of(1))
// .maximumSessionsExceededHandler(maximumSessionsExceededHandler)
//// .sessionRegistry(sessionRegistry)
http.oauth2Login { oauth2 ->
.authorizationRequestResolver(oauthAuthorizationRequestResolver)
.clientRegistrationRepository(reactiveClientRegistrationRepository)
.authorizedClientRepository(reactiveAuthorizedClientRepository)
.authorizedClientService(reactiveAuthorizedClientService)
// .authenticationSuccessHandler(loginSuccessHandler)
// .authenticationFailureHandler(loginFailureHandler)
// authorizations (all end points, apart from login and logout not permitted, unless authenticated)
http.authorizeExchange { exchange ->
.pathMatchers("/login/**", "/oauth2/**", "/logout/**").permitAll()
.pathMatchers("/login-options").permitAll()
.pathMatchers("/api/resource/**").permitAll()
.anyExchange().authenticated()
.logoutSuccessHandler { exchange, authentication ->
// indicate that the logout was successful
exchange.exchange.response.statusCode = HttpStatus.OK
// oidc backchannel logout configuraiton
http.oidcLogout { logout ->
logout.backChannel { bc ->
bc.logoutUri(bffUri + "/logout")
// apply csrf filter after the logout handler
http.addFilterAfter(csrfCookieFilter, SecurityWebFiltersOrder.LOGOUT)
<code> @Configuration
@EnableWebFluxSecurity
//@EnableRedisWebSession
@Order(Ordered.LOWEST_PRECEDENCE - 1)
internal class BffSecurityConfig () {
@Autowired
private lateinit var reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository
@Autowired
private lateinit var reactiveAuthorizedClientRepository: ServerOAuth2AuthorizedClientRepository
@Autowired
private lateinit var reactiveAuthorizedClientService: ReactiveOAuth2AuthorizedClientService
@Value("${reverse-proxy-uri}")
private lateinit var reverseProxyUri: String
@Value("${bff-uri}")
private lateinit var bffUri: String
@Bean
fun clientSecurityFilterChain(
http: ServerHttpSecurity,
serverCsrfTokenRepository: ServerCsrfTokenRepository,
spaCsrfTokenRequestHandler: SPACsrfTokenRequestHandler,
csrfCookieFilter: CsrfCookieFilter,
requestCache: RequestCache,
// sessionRegistry: SpringSessionBackedReactiveSessionRegistry<ReactiveRedisIndexedSessionRepository.RedisSession>,
// maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler,
oauthAuthorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver,
loginSuccessHandler: LoginSuccessHandler,
loginFailureHandler: LoginFailureHandler,
): SecurityWebFilterChain {
// enable csrf
http.csrf { csrf ->
csrf.csrfTokenRepository(serverCsrfTokenRepository)
csrf.csrfTokenRequestHandler(spaCsrfTokenRequestHandler)
}
// configure cors
http.cors { cors ->
cors.configurationSource {
CorsConfiguration().apply {
// ensure this matches the Angular app URL
allowedOrigins = listOf(reverseProxyUri)
allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
allowedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
exposedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
// required if credentials (cookies, authorization headers) are involved
allowCredentials = true
}
}
}
// configure request cache
// .requestCache { cache ->
// cache.requestCache(requestCache)
// }
// session management
// .sessionManagement {sessionManagement ->
// sessionManagement.concurrentSessions { sessionConcurrency ->
// sessionConcurrency
// .maximumSessions(SessionLimit.of(1))
// .maximumSessionsExceededHandler(maximumSessionsExceededHandler)
//// .sessionRegistry(sessionRegistry)
// }
// }
// oauth2.0 client login
http.oauth2Login { oauth2 ->
oauth2
.authorizationRequestResolver(oauthAuthorizationRequestResolver)
.clientRegistrationRepository(reactiveClientRegistrationRepository)
.authorizedClientRepository(reactiveAuthorizedClientRepository)
.authorizedClientService(reactiveAuthorizedClientService)
// .authenticationSuccessHandler(loginSuccessHandler)
// .authenticationFailureHandler(loginFailureHandler)
}
// authorizations (all end points, apart from login and logout not permitted, unless authenticated)
http.authorizeExchange { exchange ->
exchange
.pathMatchers("/login/**", "/oauth2/**", "/logout/**").permitAll()
.pathMatchers("/login-options").permitAll()
.pathMatchers("/api/resource/**").permitAll()
.anyExchange().authenticated()
}
// logout configuraion
http.logout { logout ->
logout
.logoutUrl("/logout")
.logoutSuccessHandler { exchange, authentication ->
// indicate that the logout was successful
exchange.exchange.response.statusCode = HttpStatus.OK
Mono.empty()
}
}
// oidc backchannel logout configuraiton
http.oidcLogout { logout ->
logout.backChannel { bc ->
bc.logoutUri(bffUri + "/logout")
}
}
// other filters
// apply csrf filter after the logout handler
http.addFilterAfter(csrfCookieFilter, SecurityWebFiltersOrder.LOGOUT)
return http.build()
}
}
</code>
@Configuration
@EnableWebFluxSecurity
//@EnableRedisWebSession
@Order(Ordered.LOWEST_PRECEDENCE - 1)
internal class BffSecurityConfig () {
@Autowired
private lateinit var reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository
@Autowired
private lateinit var reactiveAuthorizedClientRepository: ServerOAuth2AuthorizedClientRepository
@Autowired
private lateinit var reactiveAuthorizedClientService: ReactiveOAuth2AuthorizedClientService
@Value("${reverse-proxy-uri}")
private lateinit var reverseProxyUri: String
@Value("${bff-uri}")
private lateinit var bffUri: String
@Bean
fun clientSecurityFilterChain(
http: ServerHttpSecurity,
serverCsrfTokenRepository: ServerCsrfTokenRepository,
spaCsrfTokenRequestHandler: SPACsrfTokenRequestHandler,
csrfCookieFilter: CsrfCookieFilter,
requestCache: RequestCache,
// sessionRegistry: SpringSessionBackedReactiveSessionRegistry<ReactiveRedisIndexedSessionRepository.RedisSession>,
// maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler,
oauthAuthorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver,
loginSuccessHandler: LoginSuccessHandler,
loginFailureHandler: LoginFailureHandler,
): SecurityWebFilterChain {
// enable csrf
http.csrf { csrf ->
csrf.csrfTokenRepository(serverCsrfTokenRepository)
csrf.csrfTokenRequestHandler(spaCsrfTokenRequestHandler)
}
// configure cors
http.cors { cors ->
cors.configurationSource {
CorsConfiguration().apply {
// ensure this matches the Angular app URL
allowedOrigins = listOf(reverseProxyUri)
allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
allowedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
exposedHeaders = listOf("Content-Type", "Authorization", "X-XSRF-TOKEN")
// required if credentials (cookies, authorization headers) are involved
allowCredentials = true
}
}
}
// configure request cache
// .requestCache { cache ->
// cache.requestCache(requestCache)
// }
// session management
// .sessionManagement {sessionManagement ->
// sessionManagement.concurrentSessions { sessionConcurrency ->
// sessionConcurrency
// .maximumSessions(SessionLimit.of(1))
// .maximumSessionsExceededHandler(maximumSessionsExceededHandler)
//// .sessionRegistry(sessionRegistry)
// }
// }
// oauth2.0 client login
http.oauth2Login { oauth2 ->
oauth2
.authorizationRequestResolver(oauthAuthorizationRequestResolver)
.clientRegistrationRepository(reactiveClientRegistrationRepository)
.authorizedClientRepository(reactiveAuthorizedClientRepository)
.authorizedClientService(reactiveAuthorizedClientService)
// .authenticationSuccessHandler(loginSuccessHandler)
// .authenticationFailureHandler(loginFailureHandler)
}
// authorizations (all end points, apart from login and logout not permitted, unless authenticated)
http.authorizeExchange { exchange ->
exchange
.pathMatchers("/login/**", "/oauth2/**", "/logout/**").permitAll()
.pathMatchers("/login-options").permitAll()
.pathMatchers("/api/resource/**").permitAll()
.anyExchange().authenticated()
}
// logout configuraion
http.logout { logout ->
logout
.logoutUrl("/logout")
.logoutSuccessHandler { exchange, authentication ->
// indicate that the logout was successful
exchange.exchange.response.statusCode = HttpStatus.OK
Mono.empty()
}
}
// oidc backchannel logout configuraiton
http.oidcLogout { logout ->
logout.backChannel { bc ->
bc.logoutUri(bffUri + "/logout")
}
}
// other filters
// apply csrf filter after the logout handler
http.addFilterAfter(csrfCookieFilter, SecurityWebFiltersOrder.LOGOUT)
return http.build()
}
}