diff --git a/samples/boot/oauth2authorizationserver/README.adoc b/samples/boot/oauth2authorizationserver/README.adoc
new file mode 100644
index 0000000000..34e21b744f
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/README.adoc
@@ -0,0 +1,39 @@
+= OAuth 2.0 Authorization Server Sample
+
+This sample demonstrates an Authorization Server that supports a simple, static JWK Set.
+
+It's useful for working with the other samples in the library that want to point to an Authorization Server.
+
+== 1. Running the server
+
+To run the server, do:
+
+```bash
+./gradlew bootRun
+```
+
+Or import the project into your IDE and run `OAuth2AuthorizationServerApplication` from there.
+
+Once it is up, this request asks for a token with the "message:read" scope:
+
+```bash
+curl reader:secret@localhost:8081/oauth/token -d grant_type=password -d username=user -d password=password
+```
+
+Which will respond with something like:
+
+```json
+{
+ "access_token":"eyJhbGciOiJSUzI1NiIsI...Fhq4RIVyA4ZAkC7T1aZbKAQ",
+ "token_type":"bearer",
+ "expires_in":599999999,
+ "scope":"message:read",
+ "jti":"8a425df7-f4c9-4ca4-be12-0136c3015da0"
+}
+```
+
+You can also so the same with the `writer` client:
+
+```bash
+curl writer:secret@localhost:8081/oauth/token -d grant_type=password -d username=user -d password=$PASSWORD
+```
diff --git a/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle b/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle
new file mode 100644
index 0000000000..d1c378962f
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/spring-security-samples-boot-oauth2authorizationserver.gradle
@@ -0,0 +1,10 @@
+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 "org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:${springBootVersion}"
+ compile 'com.nimbusds:nimbus-jose-jwt'
+
+ testCompile 'org.springframework.boot:spring-boot-starter-test'
+}
diff --git a/samples/boot/oauth2authorizationserver/src/main/java/sample/JwkSetConfiguration.java b/samples/boot/oauth2authorizationserver/src/main/java/sample/JwkSetConfiguration.java
new file mode 100644
index 0000000000..73e59f5619
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/src/main/java/sample/JwkSetConfiguration.java
@@ -0,0 +1,220 @@
+/*
+ * 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 java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.Principal;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
+import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
+import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
+import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint;
+import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
+import org.springframework.security.oauth2.provider.token.TokenStore;
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * An instance of Legacy Authorization Server (spring-security-oauth2) that uses a single,
+ * not-rotating key and exposes a JWK endpoint.
+ *
+ * See
+ *
+ * Spring Security OAuth Autoconfig's documentation for additional detail
+ *
+ * @author Josh Cummings
+ * @since 5.1
+ */
+@EnableAuthorizationServer
+@Configuration
+public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {
+
+ AuthenticationManager authenticationManager;
+ KeyPair keyPair;
+
+ public JwkSetConfiguration(
+ AuthenticationConfiguration authenticationConfiguration,
+ KeyPair keyPair) throws Exception {
+
+ this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
+ this.keyPair = keyPair;
+ }
+
+ @Override
+ public void configure(ClientDetailsServiceConfigurer clients)
+ throws Exception {
+ // @formatter:off
+ clients.inMemory()
+ .withClient("reader")
+ .authorizedGrantTypes("password")
+ .secret("{noop}secret")
+ .scopes("message:read")
+ .accessTokenValiditySeconds(600_000_000)
+ .and()
+ .withClient("writer")
+ .authorizedGrantTypes("password")
+ .secret("{noop}secret")
+ .scopes("message:write")
+ .accessTokenValiditySeconds(600_000_000);
+ // @formatter:on
+ }
+
+ @Override
+ public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
+ // @formatter:off
+ endpoints
+ .authenticationManager(this.authenticationManager)
+ .accessTokenConverter(accessTokenConverter())
+ .tokenStore(tokenStore());
+ // @formatter:on
+ }
+
+ @Bean
+ public TokenStore tokenStore() {
+ return new JwtTokenStore(accessTokenConverter());
+ }
+
+ @Bean
+ public JwtAccessTokenConverter accessTokenConverter() {
+ JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
+ converter.setKeyPair(this.keyPair);
+
+ DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
+ accessTokenConverter.setUserTokenConverter(new SubjectAttributeUserTokenConverter());
+ converter.setAccessTokenConverter(accessTokenConverter);
+
+ return converter;
+ }
+}
+
+/**
+ * For configuring the end users recognized by this Authorization Server
+ */
+@Configuration
+class UserConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .mvcMatchers("/.well-known/jwks.json").permitAll()
+ .anyRequest().authenticated();
+ }
+
+ @Bean
+ @Override
+ public UserDetailsService userDetailsService() {
+ return new InMemoryUserDetailsManager(
+ User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build());
+ }
+}
+
+/**
+ * Legacy Authorization Server (spring-security-oauth2) does not support any
+ * JWK Set endpoint.
+ *
+ * This class adds ad-hoc support in order to better support the other samples in the repo.
+ */
+@FrameworkEndpoint
+class JwkSetEndpoint {
+ KeyPair keyPair;
+
+ public JwkSetEndpoint(KeyPair keyPair) {
+ this.keyPair = keyPair;
+ }
+
+ @GetMapping("/.well-known/jwks.json")
+ @ResponseBody
+ public Map getKey(Principal principal) {
+ RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
+ RSAKey key = new RSAKey.Builder(publicKey).build();
+ return new JWKSet(key).toJSONObject();
+ }
+}
+
+/**
+ * An Authorization Server will more typically have a key rotation strategy, and the keys will not
+ * be hard-coded into the application code.
+ *
+ * For simplicity, though, this sample doesn't demonstrate key rotation.
+ */
+@Configuration
+class KeyConfig {
+ @Bean
+ KeyPair keyPair() {
+ try {
+ String privateExponent = "3851612021791312596791631935569878540203393691253311342052463788814433805390794604753109719790052408607029530149004451377846406736413270923596916756321977922303381344613407820854322190592787335193581632323728135479679928871596911841005827348430783250026013354350760878678723915119966019947072651782000702927096735228356171563532131162414366310012554312756036441054404004920678199077822575051043273088621405687950081861819700809912238863867947415641838115425624808671834312114785499017269379478439158796130804789241476050832773822038351367878951389438751088021113551495469440016698505614123035099067172660197922333993";
+ String modulus = "18044398961479537755088511127417480155072543594514852056908450877656126120801808993616738273349107491806340290040410660515399239279742407357192875363433659810851147557504389760192273458065587503508596714389889971758652047927503525007076910925306186421971180013159326306810174367375596043267660331677530921991343349336096643043840224352451615452251387611820750171352353189973315443889352557807329336576421211370350554195530374360110583327093711721857129170040527236951522127488980970085401773781530555922385755722534685479501240842392531455355164896023070459024737908929308707435474197069199421373363801477026083786683";
+ String exponent = "65537";
+
+ RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
+ RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ return new KeyPair(factory.generatePublic(publicSpec), factory.generatePrivate(privateSpec));
+ } catch ( Exception e ) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
+
+/**
+ * Legacy Authorization Server does not support a custom name for the user parameter, so we'll need
+ * to extend the default. By default, it uses the attribute {@code user_name}, though it would be
+ * better to adhere to the {@code sub} property defined in the
+ * JWT Specification.
+ */
+class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter {
+ @Override
+ public Map convertUserAuthentication(Authentication authentication) {
+ Map response = new LinkedHashMap();
+ response.put("sub", authentication.getName());
+ if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
+ response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
+ }
+ return response;
+ }
+}
diff --git a/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java b/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java
new file mode 100644
index 0000000000..24caab6def
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/src/main/java/sample/OAuth2AuthorizationServerApplication.java
@@ -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 Josh Cummings
+ */
+@SpringBootApplication
+public class OAuth2AuthorizationServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(OAuth2AuthorizationServerApplication.class, args);
+ }
+}
diff --git a/samples/boot/oauth2authorizationserver/src/main/resources/application.yml b/samples/boot/oauth2authorizationserver/src/main/resources/application.yml
new file mode 100644
index 0000000000..e5ba667fd3
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/src/main/resources/application.yml
@@ -0,0 +1 @@
+server.port: 8081
diff --git a/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java b/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java
new file mode 100644
index 0000000000..6a663423b9
--- /dev/null
+++ b/samples/boot/oauth2authorizationserver/src/test/java/sample/OAuth2AuthorizationServerApplicationTests.java
@@ -0,0 +1,64 @@
+/*
+ * 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.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link OAuth2AuthorizationServerApplication}
+ *
+ * @author Josh Cummings
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc
+public class OAuth2AuthorizationServerApplicationTests {
+
+ @Autowired
+ MockMvc mvc;
+
+ @Test
+ public void requestTokenWhenUsingPasswordGrantTypeThenOk()
+ throws Exception {
+
+ this.mvc.perform(post("/oauth/token")
+ .param("grant_type", "password")
+ .param("username", "user")
+ .param("password", "password")
+ .header("Authorization", "Basic cmVhZGVyOnNlY3JldA=="))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void requestJwkSetWhenUsingDefaultsThenOk()
+ throws Exception {
+
+ this.mvc.perform(get("/.well-known/jwks.json"))
+ .andExpect(status().isOk());
+ }
+
+}