Skip to content

Commit

Permalink
Resource Server Opaque Token Sample
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Feb 5, 2019
1 parent fee65dc commit ae1f287
Show file tree
Hide file tree
Showing 11 changed files with 597 additions and 0 deletions.
114 changes: 114 additions & 0 deletions samples/boot/oauth2resourceserver-opaque/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
= OAuth 2.0 Resource Server Sample

This sample demonstrates integrating Resource Server with a mock Authorization Server, though it can be modified to integrate
with your favorite Authorization Server.

With it, you can run the integration tests or run the application as a stand-alone service to explore how you can
secure your own service with OAuth 2.0 Opaque Bearer Tokens using Spring Security.

== 1. Running the tests

To run the tests, do:

```bash
./gradlew integrationTest
```

Or import the project into your IDE and run `OAuth2ResourceServerApplicationTests` from there.

=== What is it doing?

By default, the tests are pointing at a mock Authorization Server instance.

The tests are configured with a set of hard-coded tokens originally obtained from the mock Authorization Server,
and each makes a query to the Resource Server with their corresponding token.

The Resource Server subsquently verifies with the Authorization Server and authorizes the request, returning the phrase

```bash
Hello, subject!
```

where "subject" is the value of the `sub` field in the JWT returned by the Authorization Server.

== 2. Running the app

To run as a stand-alone application, do:

```bash
./gradlew bootRun
```

Or import the project into your IDE and run `OAuth2ResourceServerApplication` from there.

Once it is up, you can use the following token:

```bash
export TOKEN=00ed5855-1869-47a0-b0c9-0f3ce520aee7
```

And then make this request:

```bash
curl -H "Authorization: Bearer $TOKEN" localhost:8080
```

Which will respond with the phrase:

```bash
Hello, subject!
```

where `subject` is the value of the `sub` field in the JWT returned by the Authorization Server.

Or this:

```bash
export TOKEN=b43d1500-c405-4dc9-b9c9-6cfd966c34c9

curl -H "Authorization: Bearer $TOKEN" localhost:8080/message
```

Will respond with:

```bash
secret message
```

== 2. Testing against other Authorization Servers

_In order to use this sample, your Authorization Server must support Opaque Tokens and the Introspection Endpoint.

To change the sample to point at your Authorization Server, simply find this property in the `application.yml`:

```yaml
spring:
security:
oauth2:
resourceserver:
opaque:
introspection-uri: ${mockwebserver.url}/introspect
introspection-client-id: client
introspection-client-secret: secret
```

And change the property to your Authorization Server's Introspection endpoint, including its client id and secret:

```yaml
spring:
security:
oauth2:
resourceserver:
opaque:
introspection-uri: ${mockwebserver.url}/introspect
```

And then you can run the app the same as before:

```bash
./gradlew bootRun
```

Make sure to obtain valid tokens from your Authorization Server in order to play with the sample Resource Server.
To use the `/` endpoint, any valid token from your Authorization Server will do.
To use the `/message` endpoint, the token should have the `message:read` scope.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apply plugin: 'io.spring.convention.spring-sample-boot'

dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-jose')
compile project(':spring-security-oauth2-resource-server')

compile 'org.springframework.boot:spring-boot-starter-web'
compile 'com.nimbusds:oauth2-oidc-sdk'
compile 'com.squareup.okhttp3:mockwebserver'

testCompile project(':spring-security-test')
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2002-2019 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.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* Integration tests for {@link OAuth2ResourceServerApplication}
*
* @author Josh Cummings
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class OAuth2ResourceServerApplicationITests {

String noScopesToken = "00ed5855-1869-47a0-b0c9-0f3ce520aee7";
String messageReadToken = "b43d1500-c405-4dc9-b9c9-6cfd966c34c9";

@Autowired
MockMvc mvc;

@Test
public void performWhenValidBearerTokenThenAllows()
throws Exception {

this.mvc.perform(get("/").with(bearerToken(this.noScopesToken)))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, subject!")));
}

// -- tests with scopes

@Test
public void performWhenValidBearerTokenThenScopedRequestsAlsoWork()
throws Exception {

this.mvc.perform(get("/message").with(bearerToken(this.messageReadToken)))
.andExpect(status().isOk())
.andExpect(content().string(containsString("secret message")));
}

@Test
public void performWhenInsufficientlyScopedBearerTokenThenDeniesScopedMethodAccess()
throws Exception {

this.mvc.perform(get("/message").with(bearerToken(this.noScopesToken)))
.andExpect(status().isForbidden())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE,
containsString("Bearer error=\"insufficient_scope\"")));
}

private static class BearerTokenRequestPostProcessor implements RequestPostProcessor {
private String token;

public BearerTokenRequestPostProcessor(String token) {
this.token = token;
}

@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.addHeader("Authorization", "Bearer " + this.token);
return request;
}
}

private static BearerTokenRequestPostProcessor bearerToken(String token) {
return new BearerTokenRequestPostProcessor(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2002-2019 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 org.springframework.boot.env;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;

/**
* @author Rob Winch
*/
public class MockWebServerEnvironmentPostProcessor
implements EnvironmentPostProcessor, DisposableBean {

private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource();

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
environment.getPropertySources().addFirst(this.propertySource);
}

@Override
public void destroy() throws Exception {
this.propertySource.destroy();
}
}
Loading

0 comments on commit ae1f287

Please sign in to comment.