diff --git a/samples/boot/oauth2-integration/README.adoc b/samples/boot/oauth2-integration/README.adoc new file mode 100644 index 0000000..3e28abb --- /dev/null +++ b/samples/boot/oauth2-integration/README.adoc @@ -0,0 +1,11 @@ += OAuth 2.0 Integration Sample + +This sample integrates `spring-security-oauth2-client` and `spring-security-oauth2-resource-server` with *Spring Authorization Server*. + +== Run the Sample + +* Run Authorization Server -> `./gradlew -b samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle bootRun` +** *IMPORTANT:* Make sure to modify your `/etc/hosts` file to avoid problems with session cookie overwrites between `client` and `authorizationserver`. Simply add the entry `127.0.0.1 auth-server` +* Run Resource Server -> `./gradlew -b samples/boot/oauth2-integration/resourceserver/spring-security-samples-boot-oauth2-integrated-resourceserver.gradle bootRun` +* Run Client -> `./gradlew -b samples/boot/oauth2-integration/client/spring-security-samples-boot-oauth2-integrated-client.gradle bootRun` +* Go to `http://localhost:8080` and login using *user1/password* diff --git a/samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle b/samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle new file mode 100644 index 0000000..8ed91d3 --- /dev/null +++ b/samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle @@ -0,0 +1,9 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' + +dependencies { + compile 'org.springframework.boot:spring-boot-starter-web' + compile 'org.springframework.boot:spring-boot-starter-security' + compile project(':spring-security-oauth2-authorization-server') + compile project(':spring-security-config2') + compile project(':spring-security-crypto2') +} diff --git a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java new file mode 100644 index 0000000..3bdedcc --- /dev/null +++ b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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 0.0.1 + */ +@SpringBootApplication +public class OAuth2AuthorizationServerApplication { + + public static void main(String[] args) { + SpringApplication.run(OAuth2AuthorizationServerApplication.class, args); + } + +} diff --git a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java new file mode 100644 index 0000000..fde42af --- /dev/null +++ b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 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.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +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.crypto.keys.KeyManager; +import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +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.provisioning.InMemoryUserDetailsManager; + +import java.util.UUID; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@EnableWebSecurity +@Import(OAuth2AuthorizationServerConfiguration.class) +public class AuthorizationServerConfig { + + // @formatter:off + @Bean + public RegisteredClientRepository registeredClientRepository() { + RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("messaging-client") + .clientSecret("secret") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .redirectUri("http://localhost:8080/authorized") + .scope("message.read") + .scope("message.write") + .build(); + return new InMemoryRegisteredClientRepository(registeredClient); + } + // @formatter:on + + @Bean + public KeyManager keyManager() { + return new StaticKeyGeneratingKeyManager(); + } + + // @formatter:off + @Bean + public UserDetailsService users() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("user1") + .password("password") + .roles("USER") + .build(); + return new InMemoryUserDetailsManager(user); + } + // @formatter:on +} diff --git a/samples/boot/oauth2-integration/authorizationserver/src/main/resources/application.yml b/samples/boot/oauth2-integration/authorizationserver/src/main/resources/application.yml new file mode 100644 index 0000000..5e879a6 --- /dev/null +++ b/samples/boot/oauth2-integration/authorizationserver/src/main/resources/application.yml @@ -0,0 +1,10 @@ +server: + port: 9000 + +logging: + level: + root: INFO + org.springframework.web: INFO + org.springframework.security: INFO + org.springframework.security.oauth2: INFO +# org.springframework.boot.autoconfigure: DEBUG diff --git a/samples/boot/oauth2-integration/client/spring-security-samples-boot-oauth2-integrated-client.gradle b/samples/boot/oauth2-integration/client/spring-security-samples-boot-oauth2-integrated-client.gradle new file mode 100644 index 0000000..4758bd0 --- /dev/null +++ b/samples/boot/oauth2-integration/client/spring-security-samples-boot-oauth2-integrated-client.gradle @@ -0,0 +1,14 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' + +dependencies { + compile 'org.springframework.boot:spring-boot-starter-web' + compile 'org.springframework.boot:spring-boot-starter-thymeleaf' + compile 'org.springframework.boot:spring-boot-starter-security' + compile 'org.springframework.boot:spring-boot-starter-oauth2-client' + compile 'org.springframework:spring-webflux' + compile 'io.projectreactor.netty:reactor-netty' + compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' + compile 'org.webjars:webjars-locator-core' + compile 'org.webjars:bootstrap:3.4.1' + compile 'org.webjars:jquery:3.4.1' +} diff --git a/samples/boot/oauth2-integration/client/src/main/java/sample/OAuth2ClientApplication.java b/samples/boot/oauth2-integration/client/src/main/java/sample/OAuth2ClientApplication.java new file mode 100644 index 0000000..5b0dc8c --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/java/sample/OAuth2ClientApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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 0.0.1 + */ +@SpringBootApplication +public class OAuth2ClientApplication { + + public static void main(String[] args) { + SpringApplication.run(OAuth2ClientApplication.class, args); + } + +} diff --git a/samples/boot/oauth2-integration/client/src/main/java/sample/config/SecurityConfig.java b/samples/boot/oauth2-integration/client/src/main/java/sample/config/SecurityConfig.java new file mode 100644 index 0000000..125ac9f --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/java/sample/config/SecurityConfig.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 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.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +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; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + // @formatter:off + @Override + public void configure(WebSecurity web) { + web + .ignoring() + .antMatchers("/webjars/**"); + } + // @formatter:on + + // @formatter:off + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .failureUrl("/login-error") + .permitAll() + .and() + .oauth2Client(); + } + // @formatter:on + + // @formatter:off + @Bean + public UserDetailsService users() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("user1") + .password("password") + .roles("USER") + .build(); + return new InMemoryUserDetailsManager(user); + } + // @formatter:on +} diff --git a/samples/boot/oauth2-integration/client/src/main/java/sample/config/WebClientConfig.java b/samples/boot/oauth2-integration/client/src/main/java/sample/config/WebClientConfig.java new file mode 100644 index 0000000..06f86aa --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/java/sample/config/WebClientConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@Configuration +public class WebClientConfig { + + @Bean + WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + return WebClient.builder() + .apply(oauth2Client.oauth2Configuration()) + .build(); + } + + @Bean + OAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository) { + + OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .authorizationCode() + .clientCredentials() + .build(); + DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( + clientRegistrationRepository, authorizedClientRepository); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + + return authorizedClientManager; + } +} diff --git a/samples/boot/oauth2-integration/client/src/main/java/sample/web/AuthorizationController.java b/samples/boot/oauth2-integration/client/src/main/java/sample/web/AuthorizationController.java new file mode 100644 index 0000000..3cccbc3 --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/java/sample/web/AuthorizationController.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 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.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; +import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@Controller +public class AuthorizationController { + private final WebClient webClient; + private final String messagesBaseUri; + + public AuthorizationController(WebClient webClient, + @Value("${messages.base-uri}") String messagesBaseUri) { + this.webClient = webClient; + this.messagesBaseUri = messagesBaseUri; + } + + @GetMapping(value = "/authorize", params = "grant_type=authorization_code") + public String authorizationCodeGrant(Model model, + @RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code") + OAuth2AuthorizedClient authorizedClient) { + + String[] messages = this.webClient + .get() + .uri(this.messagesBaseUri) + .attributes(oauth2AuthorizedClient(authorizedClient)) + .retrieve() + .bodyToMono(String[].class) + .block(); + model.addAttribute("messages", messages); + + return "index"; + } + + @GetMapping(value = "/authorize", params = "grant_type=client_credentials") + public String clientCredentialsGrant(Model model) { + + String[] messages = this.webClient + .get() + .uri(this.messagesBaseUri) + .attributes(clientRegistrationId("messaging-client-client-credentials")) + .retrieve() + .bodyToMono(String[].class) + .block(); + model.addAttribute("messages", messages); + + return "index"; + } +} diff --git a/samples/boot/oauth2-integration/client/src/main/java/sample/web/DefaultController.java b/samples/boot/oauth2-integration/client/src/main/java/sample/web/DefaultController.java new file mode 100644 index 0000000..d040498 --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/java/sample/web/DefaultController.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 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.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@Controller +public class DefaultController { + + @GetMapping("/") + public String root() { + return "redirect:/index"; + } + + @GetMapping("/index") + public String index() { + return "index"; + } + + @GetMapping("/login") + public String login() { + return "login"; + } + + @GetMapping("/login-error") + public String loginError(Model model) { + model.addAttribute("loginError", true); + return login(); + } +} diff --git a/samples/boot/oauth2-integration/client/src/main/resources/application.yml b/samples/boot/oauth2-integration/client/src/main/resources/application.yml new file mode 100644 index 0000000..60e6ee1 --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/resources/application.yml @@ -0,0 +1,38 @@ +server: + port: 8080 + +logging: + level: + root: INFO + org.springframework.web: INFO + org.springframework.security: INFO + org.springframework.security.oauth2: INFO +# org.springframework.boot.autoconfigure: DEBUG + +spring: + thymeleaf: + cache: false + security: + oauth2: + client: + registration: + messaging-client-authorization-code: + provider: spring + client-id: messaging-client + client-secret: secret + authorization-grant-type: authorization_code + redirect-uri: "{baseUrl}/authorized" + scope: message.read,message.write + messaging-client-client-credentials: + provider: spring + client-id: messaging-client + client-secret: secret + authorization-grant-type: client_credentials + scope: message.read,message.write + provider: + spring: + authorization-uri: http://auth-server:9000/oauth2/authorize + token-uri: http://auth-server:9000/oauth2/token + +messages: + base-uri: http://localhost:8090/messages diff --git a/samples/boot/oauth2-integration/client/src/main/resources/templates/index.html b/samples/boot/oauth2-integration/client/src/main/resources/templates/index.html new file mode 100644 index 0000000..3dcd6b3 --- /dev/null +++ b/samples/boot/oauth2-integration/client/src/main/resources/templates/index.html @@ -0,0 +1,62 @@ + + +
+Wrong username or password
+ +