diff --git a/spring-security-modules/spring-security-pkce/.gitignore b/spring-security-modules/spring-security-pkce/.gitignore new file mode 100644 index 0000000000..549e00a2a9 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-security-modules/spring-security-pkce/client/pom.xml b/spring-security-modules/spring-security-pkce/client/pom.xml new file mode 100644 index 0000000000..6961167716 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/client/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + com.baeldung + spring-security-pkce + 0.0.1-SNAPSHOT + + pkce-client + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java new file mode 100644 index 0000000000..2a23b32fc8 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.security.pkce.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PkceClientApplication { + + public static void main(String[] args) { + SpringApplication.run(PkceClientApplication.class, args); + } + +} diff --git a/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java new file mode 100644 index 0000000000..6863c13fa7 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java @@ -0,0 +1,30 @@ +package com.baeldung.security.pkce.client.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers; +import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +public class OAuth2ClientConfiguration { + + @Bean + public SecurityWebFilterChain pkceFilterChain(ServerHttpSecurity http, ServerOAuth2AuthorizationRequestResolver resolver) { + http.authorizeExchange(r -> r.anyExchange().authenticated()); + http.oauth2Login(auth -> auth.authorizationRequestResolver(resolver)); + return http.build(); + } + + @Bean + public ServerOAuth2AuthorizationRequestResolver pkceResolver(ReactiveClientRegistrationRepository repo) { + var resolver = new DefaultServerOAuth2AuthorizationRequestResolver(repo); + resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); + return resolver; + } + + +} diff --git a/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml b/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml new file mode 100644 index 0000000000..544f8254eb --- /dev/null +++ b/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml @@ -0,0 +1,13 @@ +spring: + security: + oauth2: + client: + provider: + baeldung: + issuer-uri: http://localhost:8085 + registration: + pkce: + provider: baeldung + client-id: pkce-client + client-secret: obscura + scope: openid,email \ No newline at end of file diff --git a/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html b/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html new file mode 100644 index 0000000000..9f2232ae9c --- /dev/null +++ b/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + +Baeldung :: PKCE-Client + + +

Hello from PKCE-Client

+ + \ No newline at end of file diff --git a/spring-security-modules/spring-security-pkce/pom.xml b/spring-security-modules/spring-security-pkce/pom.xml new file mode 100644 index 0000000000..7631d54354 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + spring-security-pkce + pom + spring-security-pkce + Demo project for Spring Boot + + + 2.7.2 + 11 + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + server + client + + diff --git a/spring-security-modules/spring-security-pkce/server/pom.xml b/spring-security-modules/spring-security-pkce/server/pom.xml new file mode 100644 index 0000000000..973b29f473 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + com.baeldung + spring-security-pkce + 0.0.1-SNAPSHOT + + pkce-auth-server + + + 0.3.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.security + spring-security-oauth2-authorization-server + ${spring-authorization-server.version} + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java new file mode 100644 index 0000000000..e568743838 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.security.pkce.authserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PkceAuthServerApplication { + + public static void main(String[] args) { + SpringApplication.run(PkceAuthServerApplication.class, args); + } + +} diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java new file mode 100644 index 0000000000..1f6484478d --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java @@ -0,0 +1,99 @@ +package com.baeldung.security.pkce.authserver.conf; + +import java.util.UUID; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.config.ClientSettings; +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.RequestMatcher; + +@Configuration +public class AuthServerConfiguration { + + @Bean + @Order(1) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + + var authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); + var endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + + // @formatter:off + http + .requestMatcher(endpointsMatcher) + .authorizeRequests(authorize -> + authorize + .anyRequest() + .authenticated()); + http + .exceptionHandling(exceptions -> + exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))) + .csrf( csrf -> + csrf + .ignoringRequestMatchers(endpointsMatcher)) + .apply(authorizationServerConfigurer); + + // Required by /userinfo endpoint + http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); + return http.build(); + // @formatter:on + } + + @Bean + @Order(2) + public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + return http + .formLogin(Customizer.withDefaults()) + .build(); + // @formatter:on + } + + @Bean + public RegisteredClientRepository registeredClientRepository() { + + var pkceClient = RegisteredClient + .withId(UUID.randomUUID().toString()) + .clientId("pkce-client") + .clientSecret("{noop}obscura") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .scope(OidcScopes.OPENID) + .scope(OidcScopes.EMAIL) + .scope(OidcScopes.PROFILE) + .clientSettings(ClientSettings.builder() + .requireAuthorizationConsent(false) + .requireProofKey(true) + .build()) + .redirectUri("http://127.0.0.1:8080/login/oauth2/code/pkce") // Localhost not allowed + .build(); + + return new InMemoryRegisteredClientRepository(pkceClient); + } + + @Bean + public ProviderSettings providerSettings() { + return ProviderSettings + .builder() + .build(); + } + +} diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java new file mode 100644 index 0000000000..a253141101 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java @@ -0,0 +1,53 @@ +package com.baeldung.security.pkce.authserver.conf; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.jwt.JwtDecoder; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; + +@Configuration +public class JwksConfiguration { + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + + @Bean + public JWKSource jwkSource() { + KeyPair keyPair = generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAKey rsaKey = new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + JWKSet jwkSet = new JWKSet(rsaKey); + return new ImmutableJWKSet<>(jwkSet); + } + + private static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } +} diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java new file mode 100644 index 0000000000..6877524508 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java @@ -0,0 +1,26 @@ +package com.baeldung.security.pkce.authserver.conf; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +@Configuration +public class UserDetailsConfiguration { + + @Bean + public UserDetailsService userDetailsService() { + // @formatter:off + UserDetails userDetails = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build(); + + return new InMemoryUserDetailsManager(userDetails); + // @formatter:on + } + +} diff --git a/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties b/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties new file mode 100644 index 0000000000..cd2d02be7c --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8085 diff --git a/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java b/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java new file mode 100644 index 0000000000..1a91420808 --- /dev/null +++ b/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java @@ -0,0 +1,13 @@ +package com.baeldung.security.pkce; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringSecurityPkceApplicationLiveTest { + + @Test + void whenContextLoads_thenSuccess() { + } + +}