-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SPA sample using BFF and Spring Cloud Gateway
- Loading branch information
Showing
30 changed files
with
14,035 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
spring-security.version=6.3.0 |
35 changes: 35 additions & 0 deletions
35
samples/backend-for-spa-client/samples-backend-for-spa-client.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
plugins { | ||
id "org.springframework.boot" version "3.2.2" | ||
id "io.spring.dependency-management" version "1.1.0" | ||
id "java" | ||
} | ||
|
||
group = project.rootProject.group | ||
version = project.rootProject.version | ||
|
||
java { | ||
sourceCompatibility = JavaVersion.VERSION_17 | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
maven { url "https://repo.spring.io/milestone" } | ||
maven { url "https://repo.spring.io/snapshot" } | ||
} | ||
|
||
ext { | ||
set("springCloudVersion", "2023.0.2") | ||
} | ||
|
||
dependencyManagement { | ||
imports { | ||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation "org.springframework.boot:spring-boot-starter-web" | ||
implementation "org.springframework.boot:spring-boot-starter-security" | ||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client" | ||
implementation "org.springframework.cloud:spring-cloud-starter-gateway-mvc" | ||
} |
32 changes: 32 additions & 0 deletions
32
samples/backend-for-spa-client/src/main/java/sample/BackendForSpaClientApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright 2020-2024 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
/** | ||
* @author Joe Grandja | ||
* @since 1.4 | ||
*/ | ||
@SpringBootApplication | ||
public class BackendForSpaClientApplication { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(BackendForSpaClientApplication.class, args); | ||
} | ||
|
||
} |
52 changes: 52 additions & 0 deletions
52
samples/backend-for-spa-client/src/main/java/sample/config/CorsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright 2020-2024 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample.config; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.web.cors.CorsConfiguration; | ||
import org.springframework.web.cors.CorsConfigurationSource; | ||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | ||
|
||
/** | ||
* @author Joe Grandja | ||
* @since 1.4 | ||
*/ | ||
@Configuration(proxyBeanMethods = false) | ||
public class CorsConfig { | ||
|
||
@Value("${app.base-uri}") | ||
private String appBaseUri; | ||
|
||
@Bean | ||
public CorsConfigurationSource corsConfigurationSource() { | ||
CorsConfiguration config = new CorsConfiguration(); | ||
config.addAllowedHeader("X-XSRF-TOKEN"); | ||
config.addAllowedHeader(HttpHeaders.CONTENT_TYPE); | ||
config.setAllowedMethods(Arrays.asList("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")); | ||
config.setAllowedOrigins(Collections.singletonList(this.appBaseUri)); | ||
config.setAllowCredentials(true); | ||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | ||
source.registerCorsConfiguration("/**", config); | ||
return source; | ||
} | ||
|
||
} |
64 changes: 64 additions & 0 deletions
64
samples/backend-for-spa-client/src/main/java/sample/config/GatewayFilterFunctions.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright 2020-2024 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample.config; | ||
|
||
import org.springframework.cloud.gateway.server.mvc.common.Shortcut; | ||
import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; | ||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; | ||
import org.springframework.security.oauth2.core.OAuth2AccessToken; | ||
import org.springframework.web.servlet.function.HandlerFilterFunction; | ||
import org.springframework.web.servlet.function.ServerRequest; | ||
import org.springframework.web.servlet.function.ServerResponse; | ||
|
||
import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getApplicationContext; | ||
|
||
/** | ||
* Custom {@code HandlerFilterFunction}'s registered in META-INF/spring.factories and used in application.yml. | ||
* | ||
* @author Joe Grandja | ||
* @since 1.4 | ||
*/ | ||
public interface GatewayFilterFunctions { | ||
|
||
@Shortcut | ||
static HandlerFilterFunction<ServerResponse, ServerResponse> relayTokenIfExists(String clientRegistrationId) { | ||
return (request, next) -> { | ||
Authentication principal = (Authentication) request.servletRequest().getUserPrincipal(); | ||
OAuth2AuthorizedClientRepository authorizedClientRepository = getApplicationContext(request) | ||
.getBean(OAuth2AuthorizedClientRepository.class); | ||
OAuth2AuthorizedClient authorizedClient = authorizedClientRepository.loadAuthorizedClient( | ||
clientRegistrationId, principal, request.servletRequest()); | ||
if (authorizedClient != null) { | ||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); | ||
ServerRequest bearerRequest = ServerRequest.from(request) | ||
.headers(httpHeaders -> httpHeaders.setBearerAuth(accessToken.getTokenValue())).build(); | ||
return next.handle(bearerRequest); | ||
} | ||
return next.handle(request); | ||
}; | ||
} | ||
|
||
class FilterSupplier extends SimpleFilterSupplier { | ||
|
||
FilterSupplier() { | ||
super(GatewayFilterFunctions.class); | ||
} | ||
|
||
} | ||
|
||
} |
118 changes: 118 additions & 0 deletions
118
samples/backend-for-spa-client/src/main/java/sample/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* Copyright 2020-2024 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample.config; | ||
|
||
import java.util.LinkedHashMap; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.config.Customizer; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; | ||
import org.springframework.security.web.authentication.HttpStatusEntryPoint; | ||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; | ||
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; | ||
import org.springframework.security.web.authentication.logout.LogoutHandler; | ||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; | ||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository; | ||
import org.springframework.security.web.csrf.CsrfLogoutHandler; | ||
import org.springframework.security.web.csrf.CsrfTokenRepository; | ||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; | ||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; | ||
import org.springframework.security.web.util.matcher.RequestMatcher; | ||
|
||
/** | ||
* @author Joe Grandja | ||
* @since 1.4 | ||
*/ | ||
@Configuration(proxyBeanMethods = false) | ||
@EnableWebSecurity | ||
public class SecurityConfig { | ||
|
||
@Value("${app.base-uri}") | ||
private String appBaseUri; | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); | ||
CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler(); | ||
/* | ||
IMPORTANT: | ||
Set the csrfRequestAttributeName to null, to opt-out of deferred tokens, resulting in the CsrfToken to be loaded on every request. | ||
If it does not exist, the CookieCsrfTokenRepository will automatically generate a new one and add the Cookie to the response. | ||
See the reference: https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#deferred-csrf-token | ||
*/ | ||
csrfTokenRequestAttributeHandler.setCsrfRequestAttributeName(null); | ||
|
||
// @formatter:off | ||
http | ||
.authorizeHttpRequests(authorize -> | ||
authorize | ||
.anyRequest().authenticated() | ||
) | ||
.csrf(csrf -> | ||
csrf | ||
.csrfTokenRepository(cookieCsrfTokenRepository) | ||
.csrfTokenRequestHandler(csrfTokenRequestAttributeHandler) | ||
) | ||
.cors(Customizer.withDefaults()) | ||
.exceptionHandling(exceptionHandling -> | ||
exceptionHandling | ||
.authenticationEntryPoint(authenticationEntryPoint()) | ||
) | ||
.oauth2Login(oauth2Login -> | ||
oauth2Login | ||
.successHandler(new SimpleUrlAuthenticationSuccessHandler(this.appBaseUri))) | ||
.logout(logout -> | ||
logout | ||
.addLogoutHandler(logoutHandler(cookieCsrfTokenRepository)) | ||
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)) | ||
) | ||
.oauth2Client(Customizer.withDefaults()); | ||
// @formatter:on | ||
return http.build(); | ||
} | ||
|
||
private AuthenticationEntryPoint authenticationEntryPoint() { | ||
AuthenticationEntryPoint authenticationEntryPoint = | ||
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/messaging-client-oidc"); | ||
MediaTypeRequestMatcher textHtmlMatcher = | ||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML); | ||
textHtmlMatcher.setUseEquals(true); | ||
|
||
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<>(); | ||
entryPoints.put(textHtmlMatcher, authenticationEntryPoint); | ||
|
||
DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints); | ||
delegatingAuthenticationEntryPoint.setDefaultEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); | ||
return delegatingAuthenticationEntryPoint; | ||
} | ||
|
||
private LogoutHandler logoutHandler(CsrfTokenRepository csrfTokenRepository) { | ||
return new CompositeLogoutHandler( | ||
new SecurityContextLogoutHandler(), | ||
new CsrfLogoutHandler(csrfTokenRepository)); | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
samples/backend-for-spa-client/src/main/java/sample/web/DefaultController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright 2020-2024 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample.web; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
/** | ||
* @author Joe Grandja | ||
* @since 1.4 | ||
*/ | ||
@Controller | ||
public class DefaultController { | ||
|
||
@Value("${app.base-uri}") | ||
private String appBaseUri; | ||
|
||
@GetMapping("/") | ||
public String root() { | ||
return "redirect:" + this.appBaseUri; | ||
} | ||
|
||
// '/authorized' is the registered 'redirect_uri' for authorization_code | ||
@GetMapping("/authorized") | ||
public String authorized() { | ||
return "redirect:" + this.appBaseUri; | ||
} | ||
|
||
} |
2 changes: 2 additions & 0 deletions
2
samples/backend-for-spa-client/src/main/resources/META-INF/spring.factories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\ | ||
sample.config.GatewayFilterFunctions.FilterSupplier |
Oops, something went wrong.