diff --git a/hello-service/build.gradle b/hello-service/build.gradle index 56520cc..cbd7825 100644 --- a/hello-service/build.gradle +++ b/hello-service/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.0.1' + id 'org.springframework.boot' version '3.1.0' id 'io.spring.dependency-management' version '1.1.0' } @@ -18,7 +18,7 @@ repositories { } ext { - set('springCloudVersion', "2022.0.0") + set('springCloudVersion', "2022.0.3") } dependencies { diff --git a/hello-service/gradle/wrapper/gradle-wrapper.properties b/hello-service/gradle/wrapper/gradle-wrapper.properties index 070cb70..aba097b 100644 --- a/hello-service/gradle/wrapper/gradle-wrapper.properties +++ b/hello-service/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/helm/spring-cloud-kubernetes-demo/templates/ui-interface.yml b/helm/spring-cloud-kubernetes-demo/templates/ui-interface.yml index e746919..bb291c8 100644 --- a/helm/spring-cloud-kubernetes-demo/templates/ui-interface.yml +++ b/helm/spring-cloud-kubernetes-demo/templates/ui-interface.yml @@ -5,7 +5,7 @@ metadata: namespace: {{ .Release.Namespace }} data: application.yaml: |- - testValue: it is a test value + postLogoutUrl: {{ .Values.ui.postLogoutUrl }} server: forward-headers-strategy: framework @@ -14,6 +14,24 @@ data: redis: host: {{ .Release.Namespace }}-redis-master + + security: + oauth2: + client: + registration: + client: + client-id: {{ .Values.keycloak.sso.clientId }} + client-secret: {{ .Values.keycloak.sso.clientSecret }} + client-name: keycloak + provider: keycloak + scope: + - openid + authorization-grant-type: authorization_code + provider: + keycloak: + issuer-uri: {{ .Values.keycloak.tenantUrl }} + user-name-attribute: preferred_username + cloud: gateway: routes: diff --git a/helm/spring-cloud-kubernetes-demo/values.yaml b/helm/spring-cloud-kubernetes-demo/values.yaml index fb24731..bd3d95d 100644 --- a/helm/spring-cloud-kubernetes-demo/values.yaml +++ b/helm/spring-cloud-kubernetes-demo/values.yaml @@ -14,7 +14,14 @@ helloService: pullPolicy: Always tag: latest +keycloak: + tenantUrl: http://keycloak.local + sso: + clientId: xxx + clientSecret: xxx + ui: + postLogoutUrl: http://localhost:8080 replicas: 2 image: pullPolicy: Always diff --git a/message-service/build.gradle.kts b/message-service/build.gradle.kts index 3a184b8..063033d 100644 --- a/message-service/build.gradle.kts +++ b/message-service/build.gradle.kts @@ -1,10 +1,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.springframework.boot") version "3.0.1" + id("org.springframework.boot") version "3.1.0" id("io.spring.dependency-management") version "1.1.0" - kotlin("jvm") version "1.7.22" - kotlin("plugin.spring") version "1.7.22" + kotlin("jvm") version "1.8.21" + kotlin("plugin.spring") version "1.8.21" } group = "it.valeriovaudi" @@ -14,7 +14,7 @@ repositories { mavenCentral() } -extra["springCloudVersion"] = "2022.0.0" +extra["springCloudVersion"] = "2022.0.3" dependencies { implementation("org.springframework.cloud:spring-cloud-starter-bootstrap") diff --git a/message-service/gradle/wrapper/gradle-wrapper.properties b/message-service/gradle/wrapper/gradle-wrapper.properties index 070cb70..aba097b 100644 --- a/message-service/gradle/wrapper/gradle-wrapper.properties +++ b/message-service/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/ui/pom.xml b/ui/pom.xml index c77eb74..8e7fed6 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.1 + 3.1.0 it.valeriovaudi @@ -16,10 +16,14 @@ 17 - 2022.0.0 + 2022.0.3 + + org.springframework.boot + spring-boot-starter-oauth2-client + org.springframework.cloud spring-cloud-starter-bootstrap diff --git a/ui/src/main/java/it/valeriovaudi/ui/UiApplication.java b/ui/src/main/java/it/valeriovaudi/ui/UiApplication.java index 91d80f9..a6f0ba5 100644 --- a/ui/src/main/java/it/valeriovaudi/ui/UiApplication.java +++ b/ui/src/main/java/it/valeriovaudi/ui/UiApplication.java @@ -1,16 +1,29 @@ package it.valeriovaudi.ui; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; +import reactor.core.publisher.Mono; -import static java.util.Arrays.asList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @SpringBootApplication public class UiApplication { @@ -21,34 +34,67 @@ public static void main(String[] args) { } +@EnableWebFluxSecurity @Configuration(proxyBeanMethods = false) class SecurityConfig { + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + + SecurityConfig(ReactiveClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + @Bean - public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - return http.csrf().disable().authorizeExchange() - .pathMatchers("/index.html").hasRole("USER") - .pathMatchers("/messages.html").hasRole("ADMIN") - .anyExchange().permitAll() - .and().formLogin() - .and().logout() - .and().build(); + public ReactiveOAuth2UserService oidcUserService() { + final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService(); + + + return (userRequest) -> { + // Delegate to the default implementation for loading a user + return delegate.loadUser(userRequest) + .flatMap((oidcUser) -> { + List authorities = (List) oidcUser.getClaimAsMap("realm_access").get("roles"); + Set oidcAuthorities = authorities.stream() + .map(SimpleGrantedAuthority::new) + .map(authority -> new OidcUserAuthority(authority.getAuthority(), oidcUser.getIdToken(), oidcUser.getUserInfo())) + .collect(Collectors.toSet()); + return Mono.just(new DefaultOidcUser(oidcAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo())); + }); + }; + } @Bean - public MapReactiveUserDetailsService userDetailsService() { - UserDetails user = User.builder() - .username("user") - .password("{noop}secret") - .roles("USER") - .build(); - - UserDetails admin = User.builder() - .username("admin") - .password("{noop}secret") - .roles("ADMIN") - .build(); - - return new MapReactiveUserDetailsService(asList(admin, user)); + public SecurityWebFilterChain defaultSecurityFilterChain( + @Value("${postLogoutUrl}") String postLogoutUrl, + ServerHttpSecurity http) { + http.csrf(ServerHttpSecurity.CsrfSpec::disable); + http.headers(configurer -> configurer.frameOptions(ServerHttpSecurity.HeaderSpec.FrameOptionsSpec::disable)); + + http.oauth2Login(Customizer.withDefaults()); + http.logout(logoutSpec -> { + logoutSpec.logoutSuccessHandler(oidcLogoutSuccessHandler(postLogoutUrl, clientRegistrationRepository)); + }); + + http.authorizeExchange( + auth -> + auth + .pathMatchers("/").hasAuthority("USER") + .pathMatchers("/index.html").hasAuthority("USER") + .pathMatchers("/messages.html").hasAuthority("ADMIN") + .anyExchange().permitAll() + ); + + return http.build(); } -} \ No newline at end of file + + private ServerLogoutSuccessHandler oidcLogoutSuccessHandler( + String postLogoutUrl, + ReactiveClientRegistrationRepository clientRegistrationRepository + ) { + OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler = + new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository); + oidcLogoutSuccessHandler.setPostLogoutRedirectUri(postLogoutUrl); + return oidcLogoutSuccessHandler; + } +}