Add authcodegrant-webflux sample

Issue: gh-5620
This commit is contained in:
Rob Winch 2018-08-01 12:44:19 -05:00
parent 85d5d4083f
commit 7c5c274854
10 changed files with 394 additions and 0 deletions

View File

@ -0,0 +1,64 @@
= OAuth 2.0 Authorization Code Grant Sample
== GitHub Repositories
This guide provides instructions on setting up the sample application, which leverages the OAuth 2.0 Authorization Code Grant, and displays a list of public GitHub repositories that are accessible to the authenticated user.
This includes repositories owned by the authenticated user, repositories where the authenticated user is a collaborator, and repositories that the authenticated user has access to through an organization membership.
The following sections provide detailed steps for setting up the sample and covers the following topics:
* <<github-register-application,Register OAuth application>>
* <<github-application-config,Configure application.yml>>
* <<github-boot-application,Boot up the application>>
[[github-register-application]]
=== Register OAuth application
To use GitHub's OAuth 2.0 authorization system, you must https://github.com/settings/applications/new[Register a new OAuth application].
When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/github-repos`.
The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub and have granted access to the OAuth application on the _Authorize application_ page.
[[github-application-config]]
=== Configure application.yml
Now that you have a new OAuth application with GitHub, you need to configure the sample to use the OAuth application for the _authorization code grant flow_.
To do so:
. Go to `application.yml` and set the following configuration:
+
[source,yaml]
----
spring:
security:
oauth2:
client:
registration: <1>
github: <2>
client-id: github-client-id
client-secret: github-client-secret
scope: public_repo
redirect-uri-template: "{baseUrl}/github-repos"
client-name: GitHub Repositories
----
+
.OAuth Client properties
====
<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
<2> Following the base property prefix is the ID for the `ClientRegistration`, which is github.
====
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
[[github-boot-application]]
=== Boot up the application
Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
You are then redirected to the default _auto-generated_ form login page.
Log in using *'user'* (username) and *'password'* (password) and then you'll be redirected to GitHub for authentication.
After authenticating with your GitHub credentials, the next page presented to you is "Authorize application".
This page will ask you to *Authorize* the application you created in the previous step.
Click _Authorize application_ to allow the OAuth application to access and display your public repository information.

View File

@ -0,0 +1,13 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-client')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'io.projectreactor.netty:reactor-netty'
testCompile project(':spring-security-test')
testCompile 'org.springframework.boot:spring-boot-starter-test'
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2018 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
*
* http://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.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Integration tests for the OAuth 2.0 client filters {@link OAuth2AuthorizationRequestRedirectFilter}
* and {@link OAuth2AuthorizationCodeGrantFilter}. These filters work together to realize
* the OAuth 2.0 Authorization Code Grant flow.
*
* @author Joe Grandja
* @since 5.1
*/
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureWebTestClient
@RunWith(SpringRunner.class)
public class OAuth2AuthorizationCodeGrantApplicationTests {
@Autowired
private WebTestClient rest;
@Test
@WithMockUser
public void requestWhenClientNotAuthorizedThenRedirectForAuthorization() throws Exception {
this.rest.get()
.uri("http://localhost/repos")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().valueMatches(HttpHeaders.LOCATION, "https://github.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=public_repo&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fgithub");
}
}

View File

@ -0,0 +1,20 @@
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
# org.springframework.boot.autoconfigure: DEBUG
spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
github:
client-id: client-id
client-secret: client-secret
scope: public_repo
redirect-uri-template: "{baseUrl}/authorize/oauth2/code/github"
client-name: GitHub Repositories

View File

@ -0,0 +1,30 @@
/*
* Copyright 2002-2018 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
*
* http://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 Rob Winch
*/
@SpringBootApplication
public class OAuth2AuthorizationCodeGrantApplication {
public static void main(String[] args) {
SpringApplication.run(OAuth2AuthorizationCodeGrantApplication.class, args);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2018 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
*
* http://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.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.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* @author Rob Winch
*/
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.formLogin()
.and()
.oauth2()
.client();
return http.build();
}
@Bean
ServerOAuth2AuthorizedClientRepository authorizedClientRepository(ReactiveOAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(userDetails);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2002-2018 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
*
* http://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.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author Rob Winch
* @since 5.1
*/
@Configuration
public class WebClientConfig {
@Bean
WebClient webClient() {
return WebClient.builder()
.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction())
.build();
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2018 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
*
* http://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.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 reactor.core.publisher.Mono;
import java.security.Principal;
import java.util.List;
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient;
/**
* @author Joe Grandja
* @author Rob Winch
*/
@Controller
public class GitHubReposController {
private final WebClient webClient;
public GitHubReposController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/")
public String index() {
return "redirect:/repos";
}
@GetMapping("/repos")
public String gitHubRepos(Model model, @RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient, Principal principal) {
String endpointUri = "https://api.github.com/user/repos";
Mono<List> repos = this.webClient
.get()
.uri(endpointUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(List.class);
model.addAttribute("repos", repos);
model.addAttribute("username", principal.getName());
return "github-repos";
}
}

View File

@ -0,0 +1,23 @@
server:
port: 8080
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
# org.springframework.boot.autoconfigure: DEBUG
spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
github:
client-id: your-app-client-id
client-secret: your-app-client-secret
scope: public_repo
redirect-uri-template: "{baseUrl}/authorize/oauth2/code/github"
client-name: GitHub Repositories

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security - OAuth 2.0 Authorization Code Grant</title>
<meta charset="utf-8" />
</head>
<body>
<div style="float: right" th:fragment="logout">
<div style="float:left">
<span style="font-weight:bold">User: </span><span th:text="${username}"></span>
</div>
<div style="float:none">&nbsp;</div>
<div style="float:right">
<a href="/logout">Log Out</a>
</div>
</div>
<h1>GitHub Repositories</h1>
<div>
<ul>
<li th:each="repo : ${repos}">
<span style="font-weight:bold" th:text="${repo.name}"></span>: <span th:text="${repo.url}"></span>
</li>
</ul>
</div>
</body>
</html>