Add auto-config for reactive OAuth2 Resource Server

Closes gh-13948
This commit is contained in:
Madhura Bhave 2018-08-07 15:09:33 -07:00
parent 319fec4be4
commit bc6e4e6e55
14 changed files with 558 additions and 2 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-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 org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
* support.
*
* @author Madhura Bhave
* @since 2.1.0
*/
@Configuration
@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnClass({ EnableWebFluxSecurity.class, BearerTokenAuthenticationToken.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.class,
ReactiveOAuth2ResourceServerWebSecurityConfiguration.class })
public class ReactiveOAuth2ResourceServerAutoConfiguration {
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2012-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 org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
/**
* Configures a {@link ReactiveJwtDecoder} when a JWK Set URI is available.
*
* @author Madhura Bhave
*/
@Configuration
class ReactiveOAuth2ResourceServerJwkConfiguration {
private final OAuth2ResourceServerProperties properties;
ReactiveOAuth2ResourceServerJwkConfiguration(
OAuth2ResourceServerProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnProperty(name = "spring.security.oauth2.resource.jwt.jwk.set-uri")
@ConditionalOnMissingBean
public ReactiveJwtDecoder jwtDecoder() {
return new NimbusReactiveJwtDecoder(
this.properties.getJwt().getJwk().getSetUri());
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2012-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 org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.jwt.ReactiveJwtDecoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* Configures a {@link SecurityWebFilterChain} for Reactive OAuth2 resource server support
* if a {@link ReactiveJwtDecoder} bean is present.
*
* @author Madhura Bhave
*/
@Configuration
@ConditionalOnBean(ReactiveJwtDecoder.class)
class ReactiveOAuth2ResourceServerWebSecurityConfiguration {
private final ReactiveJwtDecoder jwtDecoder;
ReactiveOAuth2ResourceServerWebSecurityConfiguration(ReactiveJwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
@Bean
@ConditionalOnMissingBean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().authenticated().and().oauth2()
.resourceServer().jwt().jwtDecoder(this.jwtDecoder);
return http.build();
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2012-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.
*/
/**
* Auto-configuration for Spring Security's Reactive OAuth2 resource server.
*/
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;

View File

@ -106,6 +106,7 @@ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\

View File

@ -0,0 +1,156 @@
/*
* Copyright 2012-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 org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.BeanIds;
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.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.WebFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveOAuth2ResourceServerAutoConfiguration}.
*
* @author Madhura Bhave
*/
public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations
.of(ReactiveOAuth2ResourceServerAutoConfiguration.class))
.withUserConfiguration(TestConfig.class);
@Test
public void autoConfigurationShouldConfigureResourceServer() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
.run((context) -> {
assertThat(context.getBean(ReactiveJwtDecoder.class))
.isInstanceOf(NimbusReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
});
}
@Test
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
this.contextRunner.run((context) -> assertThat(context)
.doesNotHaveBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN));
}
@Test
public void jwtDecoderBeanIsConditionalOnMissingBean() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
.withUserConfiguration(JwtDecoderConfig.class)
.run((this::assertFilterConfiguredWithJwtAuthenticationManager));
}
@Test
public void autoConfigurationShouldBeConditionalOnBearerTokenAuthenticationTokenClass() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
.withUserConfiguration(JwtDecoderConfig.class)
.withClassLoader(
new FilteredClassLoader(BearerTokenAuthenticationToken.class))
.run((context) -> assertThat(context)
.doesNotHaveBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN));
}
@Test
public void autoConfigurationWhenSecurityWebFilterChainConfigPresentShouldNotAddOne() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
.withUserConfiguration(SecurityWebFilterChainConfig.class)
.run((context) -> {
assertThat(context).hasSingleBean(SecurityWebFilterChain.class);
assertThat(context).hasBean("testSpringSecurityFilterChain");
});
}
@SuppressWarnings("unchecked")
private void assertFilterConfiguredWithJwtAuthenticationManager(
AssertableReactiveWebApplicationContext context) {
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
List<WebFilter> filters = (List<WebFilter>) ReflectionTestUtils
.getField(filterChain, "filters");
AuthenticationWebFilter webFilter = (AuthenticationWebFilter) filters.stream()
.filter((f) -> f instanceof AuthenticationWebFilter).findFirst()
.orElse(null);
ReactiveAuthenticationManager authenticationManager = (ReactiveAuthenticationManager) ReflectionTestUtils
.getField(webFilter, "authenticationManager");
assertThat(authenticationManager)
.isInstanceOf(JwtReactiveAuthenticationManager.class);
}
@EnableWebFluxSecurity
static class TestConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
return mock(MapReactiveUserDetailsService.class);
}
}
@Configuration
static class JwtDecoderConfig {
@Bean
public ReactiveJwtDecoder decoder() {
return mock(ReactiveJwtDecoder.class);
}
}
@Configuration
static class SecurityWebFilterChainConfig {
@Bean
SecurityWebFilterChain testSpringSecurityFilterChain(ServerHttpSecurity http,
ReactiveJwtDecoder decoder) {
http.authorizeExchange().pathMatchers("/message/**").hasRole("ADMIN")
.anyExchange().authenticated().and().oauth2().resourceServer().jwt()
.jwtDecoder(decoder);
return http.build();
}
}
}

View File

@ -165,7 +165,7 @@
<spring-plugin.version>1.2.0.RELEASE</spring-plugin.version>
<spring-restdocs.version>2.0.2.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.2.RELEASE</spring-retry.version>
<spring-security.version>5.1.0.M2</spring-security.version>
<spring-security.version>5.1.0.BUILD-SNAPSHOT</spring-security.version>
<spring-session-bom.version>Bean-M1</spring-session-bom.version>
<spring-ws.version>3.0.3.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.23.1</sqlite-jdbc.version>

View File

@ -3286,7 +3286,10 @@ following example:
spring.security.oauth2.resource.jwt.jwk.set-uri=https://example.com/oauth2/default/v1/keys
----
Alternatively, you can define your own `JwtDecoder` bean.
The same properties are applicable for both servlet and reactive applications.
Alternatively, you can define your own `JwtDecoder` bean for servlet applications
or a `ReactiveJwtDecoder` for reactive applications.

View File

@ -137,6 +137,9 @@ The following sample applications are provided:
| link:spring-boot-sample-oauth2-resource-server[spring-boot-sample-oauth2-resource-server]
| Configure an OAuth2 resource server
| link:spring-boot-sample-reactive-oauth2-resource-server[spring-boot-sample-reactive-oauth2-resource-server]
| Configure a Reactive OAuth2 resource server
| link:spring-boot-sample-parent-context[spring-boot-sample-parent-context]
| Application that uses an `ApplicationContext` with a parent

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>${revision}</version>
</parent>
<artifactId>spring-boot-sample-reactive-oauth2-resource-server</artifactId>
<name>Spring Boot Sample Reactive OAuth2 Resource Server</name>
<description>Spring Boot Sample Reactive Resource Server</description>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,32 @@
/*
* Copyright 2012-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.oauth2.resource;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@GetMapping("/")
public String index(@AuthenticationPrincipal Jwt jwt) {
return String.format("Hello, %s!", jwt.getSubject());
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2012-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.oauth2.resource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleReactiveOAuth2ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(SampleReactiveOAuth2ResourceServerApplication.class);
}
}

View File

@ -0,0 +1,8 @@
spring:
security:
oauth2:
resource:
jwt:
jwk:
# To run the application, replace this with a valid JWK Set URI
set-uri: https://example.com/oauth2/default/v1/keys

View File

@ -0,0 +1,99 @@
/*
* Copyright 2012-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.oauth2.resource;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleReactiveOAuth2ResourceServerApplicationTests {
@Autowired
private WebTestClient webTestClient;
private static MockWebServer server = new MockWebServer();
private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQi"
+ "LCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgU"
+ "OBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwIN"
+ "afU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_Ql"
+ "R44vmRqS5ncrF-1R0EGcPX49U6A";
@BeforeClass
public static void setup() throws Exception {
server.start();
String url = server.url("/.well-known/jwks.json").toString();
server.enqueue(mockResponse());
System.setProperty("spring.security.oauth2.resource.jwt.jwk.set-uri", url);
}
@AfterClass
public static void shutdown() throws Exception {
server.shutdown();
System.clearProperty("spring.security.oauth2.resource.jwt.jwk.set-uri");
}
@Test
public void getWhenValidTokenShouldBeOk() {
this.webTestClient.get().uri("/")
.headers(headers -> headers.setBearerAuth(VALID_TOKEN)).exchange()
.expectStatus().isOk().expectBody(String.class)
.isEqualTo("Hello, subject!");
}
@Test
public void getWhenNoTokenShouldBeUnauthorized() {
this.webTestClient.get().uri("/").exchange().expectStatus().isUnauthorized()
.expectHeader().valueEquals(HttpHeaders.WWW_AUTHENTICATE, "Bearer");
}
private static MockResponse mockResponse() {
String body = "{\"keys\":[{\"p\":\"2p-ViY7DE9ZrdWQb544m0Jp7Cv03YCSljqfim9pD4ALhObX0OrAznOiowTjwBky9JGffMw"
+ "DBVSfJSD9TSU7aH2sbbfi0bZLMdekKAuimudXwUqPDxrrg0BCyvCYgLmKjbVT3zcdylWSog93CNTxGDPzauu-oc0XPNKCXnaDpNvE\""
+ ",\"kty\":\"RSA\",\"q\":\"sP_QYavrpBvSJ86uoKVGj2AGl78CSsAtpf1ybSY5TwUlorXSdqapRbY69Y271b0aMLzlleUn9ZTBO"
+ "1dlKV2_dw_lPADHVia8z3pxL-8sUhIXLsgj4acchMk4c9YX-sFh07xENnyZ-_TXm3llPLuL67HUfBC2eKe800TmCYVWc9U\",\"d\""
+ ":\"bn1nFxCQT4KLTHqo8mo9HvHD0cRNRNdWcKNnnEQkCF6tKbt-ILRyQGP8O40axLd7CoNVG9c9p_-g4-2kwCtLJNv_STLtwfpCY7"
+ "VN5o6-ZIpfTjiW6duoPrLWq64Hm_4LOBQTiZfUPcLhsuJRHbWqakj-kV_YbUyC2Ocf_dd8IAQcSrAU2SCcDebhDCWwRUFvaa9V5eq0"
+ "851S9goaA-AJz-JXyePH6ZFr8JxmWkWxYZ5kdcMD-sm9ZbxE0CaEk32l4fE4hR-L8x2dDtjWA-ahKCZ091z-gV3HWtR2JOjvxoNRjxUo"
+ "3UxaGiFJHWNIl0EYUJZu1Cb-5wIlEI7wPx5mwQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"qS0OK4"
+ "8M2CIAA6_4Wdw4EbCaAfcTLf5Oy9t5BOF_PFUKqoSpZ6JsT5H0a_4zkjt-oI969v78OTlvBKbmEyKO-KeytzHBAA5CsLmVcz0THrMSg6o"
+ "XZqu66MPnvWoZN9FEN5TklPOvBFm8Bg1QZ3k-YMVaM--DLvhaYR95_mqaz50\",\"dp\":\"Too2NozLGD1XrXyhabZvy1E0EuaVFj0UHQ"
+ "PDLSpkZ_2g3BK6Art6T0xmE8RYtmqrKIEIdlI3IliAvyvAx_1D7zWTTRaj-xlZyqJFrnXWL7zj8UxT8PkB-r2E-ILZ3NAi1gxIWezlBTZ8"
+ "M6NfObDFmbTc_3tJkN_raISo8z_ziIE\",\"dq\":\"U0yhSkY5yOsa9YcMoigGVBWSJLpNHtbg5NypjHrPv8OhWbkOSq7WvSstBkF"
+ "k5AtyFvvfZLMLIkWWxxGzV0t6f1MoxBtttLrYYyCxwihiiGFhLbAdSuZ1wnxcqA9bC7UVECvrQmVTpsMs8UupfHKbQBpZ8OWAqrn"
+ "uYNNtG4_4Bt0\",\"n\":\"lygtuZj0lJjqOqIWocF8Bb583QDdq-aaFg8PesOp2-EDda6GqCpL-_NZVOflNGX7XIgjsWHcPsQHs"
+ "V9gWuOzSJ0iEuWvtQ6eGBP5M6m7pccLNZfwUse8Cb4Ngx3XiTlyuqM7pv0LPyppZusfEHVEdeelou7Dy9k0OQ_nJTI3b2E1WBoHC5"
+ "8CJ453lo4gcBm1efURN3LIVc1V9NQY_ESBKVdwqYyoJPEanURLVGRd6cQKn6YrCbbIRHjqAyqOE-z3KmgDJnPriljfR5XhSGyM9eq"
+ "D9Xpy6zu_MAeMJJfSArp857zLPk-Wf5VP9STAcjyfdBIybMKnwBYr2qHMT675hQ\"}]}";
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setResponseCode(200).setBody(body);
}
}