Remove samples
Remove in favor of the spring-projects/spring-security-samples repository. Issue gh-9539
This commit is contained in:
parent
dd3b90379b
commit
88fd834d6b
|
@ -1,11 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-rsocket')
|
||||
compile 'org.springframework.boot:spring-boot-starter-rsocket'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.rsocket.context.LocalRSocketServerPort;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.rsocket.metadata.UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Eddú Meléndez
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestPropertySource(properties = "spring.rsocket.server.port=0")
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class HelloRSocketApplicationITests {
|
||||
|
||||
@Autowired
|
||||
RSocketRequester.Builder requester;
|
||||
|
||||
@LocalRSocketServerPort
|
||||
int port;
|
||||
|
||||
@Test
|
||||
public void messageWhenAuthenticatedThenSuccess() {
|
||||
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
|
||||
RSocketRequester requester = this.requester
|
||||
.rsocketStrategies((builder) -> builder.encoder(new BasicAuthenticationEncoder()))
|
||||
.setupMetadata(credentials, BASIC_AUTHENTICATION_MIME_TYPE)
|
||||
.connectTcp("localhost", this.port)
|
||||
.block();
|
||||
|
||||
String message = requester.route("message")
|
||||
.data(Mono.empty())
|
||||
.retrieveMono(String.class)
|
||||
.block();
|
||||
|
||||
assertThat(message).isEqualTo("Hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenNotAuthenticatedThenError() {
|
||||
RSocketRequester requester = this.requester
|
||||
.connectTcp("localhost", this.port)
|
||||
.block();
|
||||
|
||||
assertThatThrownBy(() -> requester.route("message")
|
||||
.data(Mono.empty())
|
||||
.retrieveMono(String.class)
|
||||
.block())
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 Rob Winch
|
||||
* @since 5.2
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class HelloRSocketApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloRSocketApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.2
|
||||
*/
|
||||
@Configuration
|
||||
@EnableRSocketSecurity
|
||||
public class HelloRSocketSecurityConfig {
|
||||
|
||||
@Bean
|
||||
MapReactiveUserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("SETUP")
|
||||
.build();
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.2
|
||||
*/
|
||||
@Controller
|
||||
public class MessageController {
|
||||
|
||||
@MessageMapping("message")
|
||||
public Mono<String> message() {
|
||||
return Mono.just("Hello");
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
spring.rsocket.server.port=8080
|
|
@ -1,12 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'io.projectreactor:reactor-test'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 java.util.function.Consumer;
|
||||
|
||||
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.HttpStatus;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class HelloWebfluxMethodApplicationITests {
|
||||
|
||||
@Autowired
|
||||
WebTestClient rest;
|
||||
|
||||
|
||||
@Test
|
||||
public void messageWhenNotAuthenticated() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenUserThenForbidden() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.headers(robsCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenAdminThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.headers(adminCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> robsCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("rob", "rob");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> adminCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("admin", "admin");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class HelloWebfluxMethodApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloWebfluxMethodApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@Component
|
||||
public class HelloWorldMessageService {
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public Mono<String> findMessage() {
|
||||
return Mono.just("Hello World!");
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RestController
|
||||
public class MessageController {
|
||||
private final HelloWorldMessageService messages;
|
||||
|
||||
public MessageController(HelloWorldMessageService messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
@GetMapping("/message")
|
||||
public Mono<String> message() {
|
||||
return this.messages.findMessage();
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@EnableWebFluxSecurity
|
||||
@EnableReactiveMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
|
||||
return http
|
||||
// Demonstrate that method security works
|
||||
// Best practice to use both for defense in depth
|
||||
.authorizeExchange((exchanges) -> exchanges
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
.httpBasic(withDefaults())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MapReactiveUserDetailsService userDetailsService() {
|
||||
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
|
||||
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
|
||||
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
|
||||
return new MapReactiveUserDetailsService(rob, admin);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.context.ApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class HelloWebfluxMethodApplicationTests {
|
||||
WebTestClient rest;
|
||||
|
||||
@Autowired
|
||||
public void setup(ApplicationContext context) {
|
||||
this.rest = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.apply(springSecurity())
|
||||
.configureClient()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenNotAuthenticated() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenUserThenForbidden() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.headers(robsCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenAdminThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.headers(adminCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void messageWhenWithMockUserThenForbidden() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
public void messageWhenWithMockAdminThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenMutateWithMockUserThenForbidden() {
|
||||
this.rest
|
||||
.mutateWith(mockUser())
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageWhenMutateWithMockAdminThenOk() {
|
||||
this.rest
|
||||
.mutateWith(mockUser().roles("ADMIN"))
|
||||
.get()
|
||||
.uri("/message")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> robsCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("rob", "rob");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> adminCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("admin", "admin");
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class HelloWorldMessageServiceTests {
|
||||
@Autowired
|
||||
HelloWorldMessageService messages;
|
||||
|
||||
@Test
|
||||
public void messagesWhenNotAuthenticatedThenDenied() {
|
||||
StepVerifier.create(this.messages.findMessage())
|
||||
.expectError(AccessDeniedException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void messagesWhenUserThenDenied() {
|
||||
StepVerifier.create(this.messages.findMessage())
|
||||
.expectError(AccessDeniedException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
public void messagesWhenAdminThenOk() {
|
||||
StepVerifier.create(this.messages.findMessage())
|
||||
.expectNext("Hello World!")
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 java.util.function.Consumer;
|
||||
|
||||
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.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class HelloWebfluxApplicationITests {
|
||||
|
||||
@Autowired
|
||||
WebTestClient rest;
|
||||
|
||||
@Test
|
||||
public void basicWhenNoCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenValidCredentialsThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(userCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenInvalidCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(invalidCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectBody().isEmpty();
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> userCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> invalidCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID");
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RestController
|
||||
public class HelloUserController {
|
||||
|
||||
@GetMapping("/")
|
||||
public Mono<Map<String, String>> hello(Mono<Principal> principal) {
|
||||
return principal
|
||||
.map(Principal::getName)
|
||||
.map(this::helloMessage);
|
||||
}
|
||||
|
||||
private Map<String, String> helloMessage(String username) {
|
||||
return Collections.singletonMap("message", "Hello " + username + "!");
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class HelloWebfluxApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloWebfluxApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@EnableWebFluxSecurity
|
||||
public class HelloWebfluxSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public MapReactiveUserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("user")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.context.ApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
public class HelloWebfluxApplicationTests {
|
||||
WebTestClient rest;
|
||||
|
||||
@Autowired
|
||||
public void setup(ApplicationContext context) {
|
||||
this.rest = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.apply(springSecurity())
|
||||
.configureClient()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenNoCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenValidCredentialsThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(userCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenInvalidCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(invalidCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectBody().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mockSupportWhenMutateWithMockUserThenOk() {
|
||||
this.rest
|
||||
.mutateWith(mockUser())
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void mockSupportWhenWithMockUserThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> userCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> invalidCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID");
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 java.util.function.Consumer;
|
||||
|
||||
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.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class HelloWebfluxFnApplicationITests {
|
||||
|
||||
WebTestClient rest;
|
||||
|
||||
@Autowired
|
||||
public void setRest(WebTestClient rest) {
|
||||
this.rest = rest
|
||||
.mutateWith((b, h, c) -> b.filter(ExchangeFilterFunctions.basicAuthentication()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenNoCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenValidCredentialsThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(userCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenInvalidCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(invalidCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectBody().isEmpty();
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> userCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> invalidCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID");
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 java.security.Principal;
|
||||
import java.util.Collections;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@Component
|
||||
public class HelloUserController {
|
||||
|
||||
public Mono<ServerResponse> hello(ServerRequest serverRequest) {
|
||||
return serverRequest.principal()
|
||||
.map(Principal::getName)
|
||||
.flatMap((username) ->
|
||||
ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.syncBody(Collections.singletonMap("message", "Hello " + username + "!"))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class HelloWebfluxFnApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloWebfluxFnApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> routes(HelloUserController userController) {
|
||||
return route(
|
||||
GET("/"), userController::hello);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@EnableWebFluxSecurity
|
||||
public class HelloWebfluxFnSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public MapReactiveUserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("user")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.context.ApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
public class HelloWebfluxFnApplicationTests {
|
||||
|
||||
WebTestClient rest;
|
||||
|
||||
@Autowired
|
||||
public void setup(ApplicationContext context) {
|
||||
this.rest = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.apply(springSecurity())
|
||||
.configureClient()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenNoCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenValidCredentialsThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(userCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenInvalidCredentialsThenUnauthorized() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.headers(invalidCredentials())
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectBody().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mockSupportWhenMutateWithMockUserThenOk() {
|
||||
this.rest
|
||||
.mutateWith(mockUser())
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void mockSupportWhenWithMockUserThenOk() {
|
||||
this.rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().json("{\"message\":\"Hello user!\"}");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> userCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "user");
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> invalidCredentials() {
|
||||
return (httpHeaders) -> httpHeaders.setBasicAuth("user", "INVALID");
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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 org.springframework.security.samples;
|
||||
|
||||
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.mock.web.MockHttpSession;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class HelloWorldApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void accessUnprotected() throws Exception {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/index"))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessProtectedRedirectsToLogin() throws Exception {
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mockMvc.perform(get("/user/index"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(mvcResult.getResponse().getRedirectedUrl()).endsWith("/login");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUser() throws Exception {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(formLogin().user("user").password("password"))
|
||||
.andExpect(authenticated());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginInvalidUser() throws Exception {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(formLogin().user("invalid").password("invalid"))
|
||||
.andExpect(unauthenticated())
|
||||
.andExpect(status().is3xxRedirection());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUserAccessProtected() throws Exception {
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user").password("password"))
|
||||
.andExpect(authenticated()).andReturn();
|
||||
// @formatter:on
|
||||
|
||||
MockHttpSession httpSession = (MockHttpSession) mvcResult.getRequest().getSession(false);
|
||||
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/user/index").session(httpSession))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUserValidateLogout() throws Exception {
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user").password("password"))
|
||||
.andExpect(authenticated()).andReturn();
|
||||
// @formatter:on
|
||||
|
||||
MockHttpSession httpSession = (MockHttpSession) mvcResult.getRequest().getSession(false);
|
||||
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(post("/logout").with(csrf()).session(httpSession))
|
||||
.andExpect(unauthenticated());
|
||||
this.mockMvc.perform(get("/user/index").session(httpSession))
|
||||
.andExpect(unauthenticated())
|
||||
.andExpect(status().is3xxRedirection());
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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 org.springframework.security.samples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class HelloWorldApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloWorldApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.security.samples.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
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
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.antMatchers("/css/**", "/index").permitAll()
|
||||
.antMatchers("/user/**").hasRole("USER")
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.loginPage("/login")
|
||||
.failureUrl("/login-error")
|
||||
);
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(userDetails);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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 org.springframework.security.samples.web;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@Controller
|
||||
public class MainController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String root() {
|
||||
return "redirect:/index";
|
||||
}
|
||||
|
||||
@RequestMapping("/index")
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("/user/index")
|
||||
public String userIndex() {
|
||||
return "user/index";
|
||||
}
|
||||
|
||||
@RequestMapping("/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
}
|
||||
|
||||
@RequestMapping("/login-error")
|
||||
public String loginError(Model model) {
|
||||
model.addAttribute("loginError", true);
|
||||
return "login";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
org.springframework.web: INFO
|
||||
org.springframework.security: INFO
|
||||
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
font-family: sans;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
p.error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.logout {
|
||||
float: right;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
|
||||
Logged in user: <span sec:authentication="name"></span> |
|
||||
Roles: <span sec:authentication="principal.authorities"></span>
|
||||
<div>
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Hello Spring Security</h1>
|
||||
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
|
||||
<ul>
|
||||
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login page</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login page</h1>
|
||||
<p>Example user: user / password</p>
|
||||
<p th:if="${loginError}" class="error">Wrong user or password</p>
|
||||
<form th:action="@{/login}" method="post">
|
||||
<label for="username">Username</label>:
|
||||
<input type="text" id="username" name="username" autofocus="autofocus" /> <br />
|
||||
<label for="password">Password</label>:
|
||||
<input type="password" id="password" name="password" /> <br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:substituteby="index::logout"></div>
|
||||
<h1>This is a secured page!</h1>
|
||||
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,8 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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 org.springframework.security.samples;
|
||||
|
||||
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.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class InsecureApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void accessUnprotected() throws Exception {
|
||||
this.mockMvc.perform(get("/index")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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 org.springframework.security.samples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class InsecureApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(InsecureApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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 org.springframework.security.samples.web;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@Controller
|
||||
public class MainController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String root() {
|
||||
return "redirect:/index";
|
||||
}
|
||||
|
||||
@RequestMapping("/index")
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("/user/index")
|
||||
public String userIndex() {
|
||||
return "user/index";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/login", method = RequestMethod.POST)
|
||||
public String postLogin() {
|
||||
// TODO Enable form login with Spring Security (trigger error for now)
|
||||
return "redirect:/login-error";
|
||||
}
|
||||
|
||||
@RequestMapping("/login-error")
|
||||
public String loginError(Model model) {
|
||||
model.addAttribute("loginError", true);
|
||||
return "login";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
org.springframework.web: INFO
|
||||
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
font-family: sans;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
p.error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.logout {
|
||||
float: right;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello Spring Security</h1>
|
||||
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
|
||||
<ul>
|
||||
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login page</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login page</h1>
|
||||
<p>Example user: user / password</p>
|
||||
<p th:if="${loginError}" class="error">Wrong user or password</p>
|
||||
<form th:action="@{/login}" method="post">
|
||||
<label for="username">Username</label>:
|
||||
<input type="text" id="username" name="username" autofocus="autofocus" /> <br />
|
||||
<label for="password">Password</label>:
|
||||
<input type="password" id="password" name="password" /> <br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>TODO Secure this</h1>
|
||||
<p>We would like to secure this page</p>
|
||||
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,41 +0,0 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id("io.spring.convention.spring-sample-boot")
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.spring") version "1.3.71"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-security-core"))
|
||||
implementation(project(":spring-security-config"))
|
||||
implementation(project(":spring-security-web"))
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
|
||||
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
|
||||
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
|
||||
testImplementation(project(":spring-security-test"))
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test") {
|
||||
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
|
||||
}
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 org.springframework.security.samples
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class KotlinWebfluxApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<KotlinWebfluxApplication>(*args)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 org.springframework.security.samples.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.config.web.server.invoke
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/log-in", permitAll)
|
||||
authorize("/", permitAll)
|
||||
authorize("/css/**", permitAll)
|
||||
authorize("/user/**", hasAuthority("ROLE_USER"))
|
||||
}
|
||||
formLogin {
|
||||
loginPage = "/log-in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): ReactiveUserDetailsService {
|
||||
val userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(userDetails)
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 org.springframework.security.samples.web
|
||||
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
@Controller
|
||||
class MainController {
|
||||
|
||||
@GetMapping("/")
|
||||
fun index(): String {
|
||||
return "index"
|
||||
}
|
||||
|
||||
@GetMapping("/user/index")
|
||||
fun userIndex(): String {
|
||||
return "user/index"
|
||||
}
|
||||
|
||||
@GetMapping("/log-in")
|
||||
fun login(): String {
|
||||
return "login"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
|
@ -1,8 +0,0 @@
|
|||
body {
|
||||
font-family: sans;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.logout {
|
||||
float: right;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
|
||||
Logged in user: <span sec:authentication="name"></span> |
|
||||
Roles: <span sec:authentication="principal.authorities"></span>
|
||||
<div>
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Hello Spring Security</h1>
|
||||
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
|
||||
<ul>
|
||||
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login page</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login page</h1>
|
||||
<p>Example user: user / password</p>
|
||||
<form th:action="@{/log-in}" method="post">
|
||||
<label for="username">Username</label>:
|
||||
<input type="text" id="username" name="username" autofocus="autofocus" /> <br />
|
||||
<label for="password">Password</label>:
|
||||
<input type="password" id="password" name="password" /> <br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
<p><a href="/" th:href="@{/}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:substituteby="index::logout"></div>
|
||||
<h1>This is a secured page!</h1>
|
||||
<p><a href="/" th:href="@{/}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 org.springframework.security.samples
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
|
||||
@SpringBootTest
|
||||
class KotlinWebfluxApplicationTests {
|
||||
|
||||
lateinit var rest: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
rest = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `index page is not protected`() {
|
||||
rest
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protected page when unauthenticated then redirects to login `() {
|
||||
rest
|
||||
.get()
|
||||
.uri("/user/index")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.expectHeader().valueEquals("Location", "/log-in")
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun `protected page can be accessed when authenticated`() {
|
||||
rest
|
||||
.get()
|
||||
.uri("/user/index")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id("io.spring.convention.spring-sample-boot")
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.spring") version "1.3.71"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-security-core"))
|
||||
implementation(project(":spring-security-config"))
|
||||
implementation(project(":spring-security-web"))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
|
||||
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
testImplementation(project(":spring-security-test"))
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.security.samples
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
/**
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@SpringBootApplication
|
||||
class KotlinApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<KotlinApplication>(*args)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.security.samples.config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.config.web.servlet.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
|
||||
/**
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
class SecurityConfig : WebSecurityConfigurerAdapter() {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/css/**", permitAll)
|
||||
authorize("/user/**", hasAuthority("ROLE_USER"))
|
||||
}
|
||||
formLogin {
|
||||
loginPage = "/log-in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public override fun userDetailsService(): UserDetailsService {
|
||||
val userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return InMemoryUserDetailsManager(userDetails)
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.security.samples.web
|
||||
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
/**
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@Controller
|
||||
class MainController {
|
||||
|
||||
@GetMapping("/")
|
||||
fun index(): String {
|
||||
return "index"
|
||||
}
|
||||
|
||||
@GetMapping("/user/index")
|
||||
fun userIndex(): String {
|
||||
return "user/index"
|
||||
}
|
||||
|
||||
@GetMapping("/log-in")
|
||||
fun login(): String {
|
||||
return "login"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
|
@ -1,8 +0,0 @@
|
|||
body {
|
||||
font-family: sans;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.logout {
|
||||
float: right;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<!--
|
||||
~ 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
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
|
||||
Logged in user: <span sec:authentication="name"></span> |
|
||||
Roles: <span sec:authentication="principal.authorities"></span>
|
||||
<div>
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Hello Spring Security</h1>
|
||||
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
|
||||
<ul>
|
||||
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login page</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login page</h1>
|
||||
<p>Example user: user / password</p>
|
||||
<form th:action="@{/log-in}" method="post">
|
||||
<label for="username">Username</label>:
|
||||
<input type="text" id="username" name="username" autofocus="autofocus" /> <br />
|
||||
<label for="password">Password</label>:
|
||||
<input type="password" id="password" name="password" /> <br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
<p><a href="/" th:href="@{/}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||
<!--
|
||||
~ 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
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Hello Spring Security</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:substituteby="index::logout"></div>
|
||||
<h1>This is a secured page!</h1>
|
||||
<p><a href="/" th:href="@{/}">Back to home page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.security.samples
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
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.mock.web.MockHttpSession
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class KotlinApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Test
|
||||
fun `index page is not protected`() {
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
status { isOk() }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protected page redirects to login`() {
|
||||
val mvcResult = this.mockMvc.get("/user/index")
|
||||
.andExpect { status { is3xxRedirection() } }
|
||||
.andReturn()
|
||||
|
||||
assertThat(mvcResult.response.redirectedUrl).endsWith("/log-in")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid user permitted to log in`() {
|
||||
this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
|
||||
.andExpect(authenticated())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalid user not permitted to log in`() {
|
||||
this.mockMvc.perform(formLogin("/log-in").user("invalid").password("invalid"))
|
||||
.andExpect(unauthenticated())
|
||||
.andExpect(status().is3xxRedirection)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logged in user can access protected page`() {
|
||||
val mvcResult = this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
|
||||
.andExpect(authenticated()).andReturn()
|
||||
|
||||
val httpSession = mvcResult.request.getSession(false) as MockHttpSession
|
||||
|
||||
this.mockMvc.get("/user/index") {
|
||||
session = httpSession
|
||||
}.andExpect {
|
||||
status { isOk() }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
= 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=subject -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 do the same with the `writer` client:
|
||||
|
||||
```bash
|
||||
curl writer:secret@localhost:8081/oauth/token -d grant_type=password -d username=subject -d password=password
|
||||
```
|
|
@ -1,14 +0,0 @@
|
|||
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 'javax.xml.bind:jaxb-api'
|
||||
compile 'com.sun.xml.bind:jaxb-core'
|
||||
compile 'com.sun.xml.bind:jaxb-impl'
|
||||
compile 'com.nimbusds:nimbus-jose-jwt'
|
||||
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.RSAPrivateKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.common.OAuth2AccessToken;
|
||||
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.OAuth2Authentication;
|
||||
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.InMemoryTokenStore;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
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
|
||||
* <a
|
||||
* target="_blank"
|
||||
* href="https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/">
|
||||
* Spring Security OAuth Autoconfig's documentation</a> for additional detail
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.1
|
||||
*/
|
||||
@EnableAuthorizationServer
|
||||
@Configuration
|
||||
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
|
||||
|
||||
AuthenticationManager authenticationManager;
|
||||
KeyPair keyPair;
|
||||
boolean jwtEnabled;
|
||||
|
||||
public AuthorizationServerConfiguration(
|
||||
AuthenticationConfiguration authenticationConfiguration,
|
||||
KeyPair keyPair,
|
||||
@Value("${security.oauth2.authorizationserver.jwt.enabled:true}") boolean jwtEnabled) throws Exception {
|
||||
|
||||
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
|
||||
this.keyPair = keyPair;
|
||||
this.jwtEnabled = jwtEnabled;
|
||||
}
|
||||
|
||||
@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)
|
||||
.and()
|
||||
.withClient("noscopes")
|
||||
.authorizedGrantTypes("password")
|
||||
.secret("{noop}secret")
|
||||
.scopes("none")
|
||||
.accessTokenValiditySeconds(600_000_000);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
|
||||
// @formatter:off
|
||||
endpoints
|
||||
.authenticationManager(this.authenticationManager)
|
||||
.tokenStore(tokenStore());
|
||||
|
||||
if (this.jwtEnabled) {
|
||||
endpoints
|
||||
.accessTokenConverter(accessTokenConverter());
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore() {
|
||||
if (this.jwtEnabled) {
|
||||
return new JwtTokenStore(accessTokenConverter());
|
||||
} else {
|
||||
return new InMemoryTokenStore();
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.csrf().ignoringRequestMatchers((request) -> "/introspect".equals(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(
|
||||
User.withDefaultPasswordEncoder()
|
||||
.username("subject")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy Authorization Server (spring-security-oauth2) does not support any
|
||||
* Token Introspection endpoint.
|
||||
*
|
||||
* This class adds ad-hoc support in order to better support the other samples in the repo.
|
||||
*/
|
||||
@FrameworkEndpoint
|
||||
class IntrospectEndpoint {
|
||||
TokenStore tokenStore;
|
||||
|
||||
IntrospectEndpoint(TokenStore tokenStore) {
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
@PostMapping("/introspect")
|
||||
@ResponseBody
|
||||
public Map<String, Object> introspect(@RequestParam("token") String token) {
|
||||
OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(token);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
if (accessToken == null || accessToken.isExpired()) {
|
||||
attributes.put("active", false);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
OAuth2Authentication authentication = this.tokenStore.readAuthentication(token);
|
||||
|
||||
attributes.put("active", true);
|
||||
attributes.put("exp", accessToken.getExpiration().getTime());
|
||||
attributes.put("scope", accessToken.getScope().stream().collect(Collectors.joining(" ")));
|
||||
attributes.put("sub", authentication.getName());
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy Authorization Server (spring-security-oauth2) does not support any
|
||||
* <a href target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> endpoint.
|
||||
*
|
||||
* This class adds ad-hoc support in order to better support the other samples in the repo.
|
||||
*/
|
||||
@FrameworkEndpoint
|
||||
class JwkSetEndpoint {
|
||||
KeyPair keyPair;
|
||||
|
||||
JwkSetEndpoint(KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
}
|
||||
|
||||
@GetMapping("/.well-known/jwks.json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getKey() {
|
||||
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
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JWT Specification</a>.
|
||||
*/
|
||||
class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter {
|
||||
@Override
|
||||
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("sub", authentication.getName());
|
||||
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
|
||||
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 Josh Cummings
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2AuthorizationServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OAuth2AuthorizationServerApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
server.port: 8081
|
||||
|
||||
# security.oauth2.authorizationserver.jwt.enabled: false
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.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", "subject")
|
||||
.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());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,324 +0,0 @@
|
|||
NOTE: Spring Security Reactive OAuth only supports authentication using a user info endpoint.
|
||||
Support for JWT validation will be added in https://github.com/spring-projects/spring-security/issues/5330[gh-5330].
|
||||
|
||||
= OAuth 2.0 Login Sample
|
||||
|
||||
This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
|
||||
The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
|
||||
|
||||
The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
|
||||
|
||||
* <<google-login, Google>>
|
||||
* <<github-login, GitHub>>
|
||||
* <<facebook-login, Facebook>>
|
||||
* <<okta-login, Okta>>
|
||||
|
||||
[[google-login]]
|
||||
== Login with Google
|
||||
|
||||
This section shows how to configure the sample application using Google as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<google-initial-setup,Initial setup>>
|
||||
* <<google-redirect-uri,Setting the redirect URI>>
|
||||
* <<google-application-config,Configure application.yml>>
|
||||
* <<google-boot-application,Boot up the application>>
|
||||
|
||||
[[google-initial-setup]]
|
||||
=== Initial setup
|
||||
|
||||
To use Google's OAuth 2.0 authentication system for login, you must set up a project in the Google API Console to obtain OAuth 2.0 credentials.
|
||||
|
||||
NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the
|
||||
https://openid.net/connect/[OpenID Connect 1.0] specification and is https://openid.net/certification/[OpenID Certified].
|
||||
|
||||
Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page, starting in the section, "Setting up OAuth 2.0".
|
||||
|
||||
After completing the "Obtain OAuth 2.0 credentials" instructions, you should have a new OAuth Client with credentials consisting of a Client ID and a Client Secret.
|
||||
|
||||
[[google-redirect-uri]]
|
||||
=== Setting the redirect URI
|
||||
|
||||
The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
|
||||
and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
|
||||
|
||||
In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[google-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have a new OAuth Client with Google, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
google: <2>
|
||||
client-id: google-client-id
|
||||
client-secret: google-client-secret
|
||||
----
|
||||
+
|
||||
.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`, such as google.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
|
||||
[[google-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_ login page, which displays a link for Google.
|
||||
|
||||
Click on the Google link, and you are then redirected to Google for authentication.
|
||||
|
||||
After authenticating with your Google account credentials, the next page presented to you is the Consent screen.
|
||||
The Consent screen asks you to either allow or deny access to the OAuth Client you created earlier.
|
||||
Click *Allow* to authorize the OAuth Client to access your email address and basic profile information.
|
||||
|
||||
At this point, the OAuth Client retrieves your email address and basic profile information
|
||||
from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session.
|
||||
|
||||
[[github-login]]
|
||||
== Login with GitHub
|
||||
|
||||
This section shows how to configure the sample application using GitHub as the Authentication Provider 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 authentication system for login, 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/login/oauth2/code/github`.
|
||||
|
||||
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.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[github-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have a new OAuth application with GitHub, you need to configure the application to use the OAuth application for the _authentication 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
|
||||
----
|
||||
+
|
||||
.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`, such as 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_ login page, which displays a link for GitHub.
|
||||
|
||||
Click on the GitHub link, and you are then 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 your personal user data information.
|
||||
|
||||
At this point, the OAuth Client retrieves your personal user information
|
||||
from the UserInfo Endpoint and establishes an authenticated session.
|
||||
|
||||
[TIP]
|
||||
For detailed information returned from the UserInfo Endpoint, see the API documentation
|
||||
for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"].
|
||||
|
||||
[[facebook-login]]
|
||||
== Login with Facebook
|
||||
|
||||
This section shows how to configure the sample application using Facebook as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<facebook-register-application,Add a New App>>
|
||||
* <<facebook-application-config,Configure application.yml>>
|
||||
* <<facebook-boot-application,Boot up the application>>
|
||||
|
||||
[[facebook-register-application]]
|
||||
=== Add a New App
|
||||
|
||||
To use Facebook's OAuth 2.0 authentication system for login, you must first https://developers.facebook.com/apps[Add a New App].
|
||||
|
||||
Select "Create a New App" and then the "Create a New App ID" page is presented. Enter the Display Name, Contact Email, Category and then click "Create App ID".
|
||||
|
||||
NOTE: The selection for the _Category_ field is not relevant but it's a required field - select "Local".
|
||||
|
||||
The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
|
||||
In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
|
||||
|
||||
For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_.
|
||||
|
||||
The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
|
||||
and have granted access to the application on the _Authorize application_ page.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[facebook-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have created a new application with Facebook, you need to configure the sample application to use the application for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
facebook: <2>
|
||||
client-id: facebook-client-id
|
||||
client-secret: facebook-client-secret
|
||||
----
|
||||
+
|
||||
.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`, such as facebook.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
|
||||
[[facebook-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_ login page, which displays a link for Facebook.
|
||||
|
||||
Click on the Facebook link, and you are then redirected to Facebook for authentication.
|
||||
|
||||
After authenticating with your Facebook 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 your _public profile_ and _email address_ information.
|
||||
|
||||
At this point, the OAuth Client retrieves your personal user information
|
||||
from the UserInfo Endpoint and establishes an authenticated session.
|
||||
|
||||
[[okta-login]]
|
||||
== Login with Okta
|
||||
|
||||
This section shows how to configure the sample application using Okta as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<okta-register-application,Add Application>>
|
||||
* <<okta-assign-application-people,Assign Application to People>>
|
||||
* <<okta-application-config,Configure application.yml>>
|
||||
* <<okta-boot-application,Boot up the application>>
|
||||
|
||||
[[okta-register-application]]
|
||||
=== Add Application
|
||||
|
||||
To use Okta's OAuth 2.0 authentication system for login, you must first https://www.okta.com/developer/signup[create a developer account].
|
||||
|
||||
Sign in to your account sub-domain and navigate to _Applications -> Applications_ and then select the "Add Application" button.
|
||||
From the "Add Application" page, select the "Create New App" button and enter the following:
|
||||
|
||||
* *Platform:* Web
|
||||
* *Sign on method:* OpenID Connect
|
||||
|
||||
Select the _Create_ button.
|
||||
On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
|
||||
On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
|
||||
|
||||
The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
|
||||
and have granted access to the application on the _Authorize application_ page.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[okta-assign-application-people]]
|
||||
=== Assign Application to People
|
||||
|
||||
From the "General" tab of the application, select the "Assignments" tab and then select the _Assign_ button.
|
||||
Select _Assign to People_ and assign your account to the application. Then select the _Save and Go Back_ button.
|
||||
|
||||
[[okta-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have created a new application with Okta, you need to configure the sample application to use the application for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
okta: <2>
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
provider: <3>
|
||||
okta:
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
user-name-attribute: sub
|
||||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
||||
----
|
||||
+
|
||||
.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`, such as okta.
|
||||
<3> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`, `token-uri`, `user-info-uri` and `jwk-set-uri` with the sub-domain assigned to your account during the registration process.
|
||||
|
||||
[[okta-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_ login page, which displays a link for Okta.
|
||||
|
||||
Click on the Okta link, and you are then redirected to Okta for authentication.
|
||||
|
||||
After authenticating with your Okta account credentials, the OAuth Client retrieves your email address and basic profile information
|
||||
from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session.
|
|
@ -1,14 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-oauth2-client')
|
||||
compile project(':spring-security-oauth2-jose')
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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.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.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.hamcrest.core.StringContains.containsString;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveOAuth2LoginApplication}
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
public class OAuth2LoginApplicationTests {
|
||||
|
||||
@Autowired
|
||||
WebTestClient test;
|
||||
|
||||
@Autowired
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Test
|
||||
public void requestWhenMockOidcLoginThenIndex() {
|
||||
this.clientRegistrationRepository.findByRegistrationId("github")
|
||||
.map((clientRegistration) ->
|
||||
this.test.mutateWith(mockOAuth2Login().clientRegistration(clientRegistration))
|
||||
.get().uri("/")
|
||||
.exchange()
|
||||
.expectBody(String.class).value(containsString("GitHub"))
|
||||
).block();
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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 Rob Winch
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class ReactiveOAuth2LoginApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ReactiveOAuth2LoginApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Controller
|
||||
public class OAuth2LoginController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Model model,
|
||||
@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
|
||||
@AuthenticationPrincipal OAuth2User oauth2User) {
|
||||
model.addAttribute("userName", oauth2User.getName());
|
||||
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
|
||||
model.addAttribute("userAttributes", oauth2User.getAttributes());
|
||||
return "index";
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
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:
|
||||
google:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
github:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
facebook:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
okta:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
provider:
|
||||
okta:
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
|
@ -1,51 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
~ /*
|
||||
~ * 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
|
||||
~ *
|
||||
~ * 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.
|
||||
~ */
|
||||
~
|
||||
-->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Spring Security - OAuth 2.0 Login</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"> </div>
|
||||
<div style="float:right">
|
||||
<a th:href="@{/logout}">Log Out</a>
|
||||
</div>
|
||||
</div>
|
||||
<h1>OAuth 2.0 Login with Spring Security</h1>
|
||||
<div>
|
||||
You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>
|
||||
via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>
|
||||
<span style="font-weight:bold">User Attributes:</span>
|
||||
<ul>
|
||||
<li th:each="userAttribute : ${userAttributes}">
|
||||
<span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import sample.web.OAuth2LoginController;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebFluxTest(OAuth2LoginController.class)
|
||||
public class OAuth2LoginControllerTests {
|
||||
|
||||
@Autowired
|
||||
OAuth2LoginController controller;
|
||||
|
||||
@Autowired
|
||||
ViewResolver viewResolver;
|
||||
|
||||
@Mock
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Mock
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
WebTestClient rest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.rest = WebTestClient
|
||||
.bindToController(this.controller)
|
||||
.apply(springSecurity())
|
||||
.webFilter(new SecurityContextServerWebExchangeWebFilter())
|
||||
.argumentResolvers((c) -> {
|
||||
c.addCustomResolver(new AuthenticationPrincipalArgumentResolver(new ReactiveAdapterRegistry()));
|
||||
c.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver
|
||||
(this.clientRegistrationRepository, this.authorizedClientRepository));
|
||||
})
|
||||
.viewResolvers((c) -> c.viewResolver(this.viewResolver))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexGreetsAuthenticatedUser() {
|
||||
this.rest.mutateWith(mockOAuth2Login())
|
||||
.get().uri("/").exchange()
|
||||
.expectBody(String.class).value(containsString("user"));
|
||||
}
|
||||
}
|
|
@ -1,321 +0,0 @@
|
|||
= OAuth 2.0 Login Sample
|
||||
|
||||
This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
|
||||
The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
|
||||
|
||||
The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
|
||||
|
||||
* <<google-login, Google>>
|
||||
* <<github-login, GitHub>>
|
||||
* <<facebook-login, Facebook>>
|
||||
* <<okta-login, Okta>>
|
||||
|
||||
[[google-login]]
|
||||
== Login with Google
|
||||
|
||||
This section shows how to configure the sample application using Google as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<google-initial-setup,Initial setup>>
|
||||
* <<google-redirect-uri,Setting the redirect URI>>
|
||||
* <<google-application-config,Configure application.yml>>
|
||||
* <<google-boot-application,Boot up the application>>
|
||||
|
||||
[[google-initial-setup]]
|
||||
=== Initial setup
|
||||
|
||||
To use Google's OAuth 2.0 authentication system for login, you must set up a project in the Google API Console to obtain OAuth 2.0 credentials.
|
||||
|
||||
NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the
|
||||
https://openid.net/connect/[OpenID Connect 1.0] specification and is https://openid.net/certification/[OpenID Certified].
|
||||
|
||||
Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page, starting in the section, "Setting up OAuth 2.0".
|
||||
|
||||
After completing the "Obtain OAuth 2.0 credentials" instructions, you should have a new OAuth Client with credentials consisting of a Client ID and a Client Secret.
|
||||
|
||||
[[google-redirect-uri]]
|
||||
=== Setting the redirect URI
|
||||
|
||||
The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
|
||||
and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
|
||||
|
||||
In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[google-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have a new OAuth Client with Google, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
google: <2>
|
||||
client-id: google-client-id
|
||||
client-secret: google-client-secret
|
||||
----
|
||||
+
|
||||
.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`, such as google.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
|
||||
[[google-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_ login page, which displays a link for Google.
|
||||
|
||||
Click on the Google link, and you are then redirected to Google for authentication.
|
||||
|
||||
After authenticating with your Google account credentials, the next page presented to you is the Consent screen.
|
||||
The Consent screen asks you to either allow or deny access to the OAuth Client you created earlier.
|
||||
Click *Allow* to authorize the OAuth Client to access your email address and basic profile information.
|
||||
|
||||
At this point, the OAuth Client retrieves your email address and basic profile information
|
||||
from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session.
|
||||
|
||||
[[github-login]]
|
||||
== Login with GitHub
|
||||
|
||||
This section shows how to configure the sample application using GitHub as the Authentication Provider 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 authentication system for login, 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/login/oauth2/code/github`.
|
||||
|
||||
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.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[github-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have a new OAuth application with GitHub, you need to configure the application to use the OAuth application for the _authentication 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
|
||||
----
|
||||
+
|
||||
.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`, such as 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_ login page, which displays a link for GitHub.
|
||||
|
||||
Click on the GitHub link, and you are then 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 your personal user data information.
|
||||
|
||||
At this point, the OAuth Client retrieves your personal user information
|
||||
from the UserInfo Endpoint and establishes an authenticated session.
|
||||
|
||||
[TIP]
|
||||
For detailed information returned from the UserInfo Endpoint, see the API documentation
|
||||
for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"].
|
||||
|
||||
[[facebook-login]]
|
||||
== Login with Facebook
|
||||
|
||||
This section shows how to configure the sample application using Facebook as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<facebook-register-application,Add a New App>>
|
||||
* <<facebook-application-config,Configure application.yml>>
|
||||
* <<facebook-boot-application,Boot up the application>>
|
||||
|
||||
[[facebook-register-application]]
|
||||
=== Add a New App
|
||||
|
||||
To use Facebook's OAuth 2.0 authentication system for login, you must first https://developers.facebook.com/apps[Add a New App].
|
||||
|
||||
Select "Create a New App" and then the "Create a New App ID" page is presented. Enter the Display Name, Contact Email, Category and then click "Create App ID".
|
||||
|
||||
NOTE: The selection for the _Category_ field is not relevant but it's a required field - select "Local".
|
||||
|
||||
The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
|
||||
In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
|
||||
|
||||
For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_.
|
||||
|
||||
The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
|
||||
and have granted access to the application on the _Authorize application_ page.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[facebook-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have created a new application with Facebook, you need to configure the sample application to use the application for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
facebook: <2>
|
||||
client-id: facebook-client-id
|
||||
client-secret: facebook-client-secret
|
||||
----
|
||||
+
|
||||
.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`, such as facebook.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
|
||||
[[facebook-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_ login page, which displays a link for Facebook.
|
||||
|
||||
Click on the Facebook link, and you are then redirected to Facebook for authentication.
|
||||
|
||||
After authenticating with your Facebook 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 your _public profile_ and _email address_ information.
|
||||
|
||||
At this point, the OAuth Client retrieves your personal user information
|
||||
from the UserInfo Endpoint and establishes an authenticated session.
|
||||
|
||||
[[okta-login]]
|
||||
== Login with Okta
|
||||
|
||||
This section shows how to configure the sample application using Okta as the Authentication Provider and covers the following topics:
|
||||
|
||||
* <<okta-register-application,Add Application>>
|
||||
* <<okta-assign-application-people,Assign Application to People>>
|
||||
* <<okta-application-config,Configure application.yml>>
|
||||
* <<okta-boot-application,Boot up the application>>
|
||||
|
||||
[[okta-register-application]]
|
||||
=== Add Application
|
||||
|
||||
To use Okta's OAuth 2.0 authentication system for login, you must first https://www.okta.com/developer/signup[create a developer account].
|
||||
|
||||
Sign in to your account sub-domain and navigate to _Applications -> Applications_ and then select the "Add Application" button.
|
||||
From the "Add Application" page, select the "Create New App" button and enter the following:
|
||||
|
||||
* *Platform:* Web
|
||||
* *Sign on method:* OpenID Connect
|
||||
|
||||
Select the _Create_ button.
|
||||
On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
|
||||
On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
|
||||
|
||||
The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
|
||||
and have granted access to the application on the _Authorize application_ page.
|
||||
|
||||
TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
|
||||
The *_registrationId_* is a unique identifier for the `ClientRegistration`.
|
||||
|
||||
IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
|
||||
Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
|
||||
|
||||
[[okta-assign-application-people]]
|
||||
=== Assign Application to People
|
||||
|
||||
From the "General" tab of the application, select the "Assignments" tab and then select the _Assign_ button.
|
||||
Select _Assign to People_ and assign your account to the application. Then select the _Save and Go Back_ button.
|
||||
|
||||
[[okta-application-config]]
|
||||
=== Configure application.yml
|
||||
|
||||
Now that you have created a new application with Okta, you need to configure the sample application to use the application for the _authentication flow_. To do so:
|
||||
|
||||
. Go to `application.yml` and set the following configuration:
|
||||
+
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration: <1>
|
||||
okta: <2>
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
provider: <3>
|
||||
okta:
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
user-name-attribute: sub
|
||||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
||||
----
|
||||
+
|
||||
.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`, such as okta.
|
||||
<3> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`, `token-uri`, `user-info-uri` and `jwk-set-uri` with the sub-domain assigned to your account during the registration process.
|
||||
|
||||
[[okta-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_ login page, which displays a link for Okta.
|
||||
|
||||
Click on the Okta link, and you are then redirected to Okta for authentication.
|
||||
|
||||
After authenticating with your Okta account credentials, the OAuth Client retrieves your email address and basic profile information
|
||||
from the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] and establishes an authenticated session.
|
|
@ -1,14 +0,0 @@
|
|||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-oauth2-client')
|
||||
compile project(':spring-security-oauth2-jose')
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.WebResponse;
|
||||
import com.gargoylesoftware.htmlunit.html.DomNodeList;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlElement;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import org.junit.Before;
|
||||
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.boot.test.context.TestConfiguration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
|
||||
/**
|
||||
* Integration tests for the OAuth 2.0 client filters {@link OAuth2AuthorizationRequestRedirectFilter}
|
||||
* and {@link OAuth2LoginAuthenticationFilter}. These filters work together to realize
|
||||
* OAuth 2.0 Login leveraging the Authorization Code Grant flow.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class OAuth2LoginApplicationTests {
|
||||
private static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization";
|
||||
private static final String AUTHORIZE_BASE_URL = "http://localhost:8080/login/oauth2/code";
|
||||
|
||||
@Autowired
|
||||
private WebClient webClient;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webClient.getCookieManager().clearCookies();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestIndexPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
this.assertLoginPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestOtherPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/other-page");
|
||||
this.assertLoginPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizeGitHubClientWhenLinkClickedThenStatusRedirectForAuthorization() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
|
||||
WebResponse response = this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
|
||||
|
||||
String authorizeRedirectUri = response.getResponseHeaderValue("Location");
|
||||
assertThat(authorizeRedirectUri).isNotNull();
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUri(URI.create(authorizeRedirectUri)).build();
|
||||
|
||||
String requestUri = uriComponents.getScheme() + "://" + uriComponents.getHost() + uriComponents.getPath();
|
||||
assertThat(requestUri).isEqualTo(clientRegistration.getProviderDetails().getAuthorizationUri());
|
||||
|
||||
Map<String, String> params = uriComponents.getQueryParams().toSingleValueMap();
|
||||
|
||||
assertThat(params.get(OAuth2ParameterNames.RESPONSE_TYPE)).isEqualTo(OAuth2AuthorizationResponseType.CODE.getValue());
|
||||
assertThat(params.get(OAuth2ParameterNames.CLIENT_ID)).isEqualTo(clientRegistration.getClientId());
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId();
|
||||
assertThat(URLDecoder.decode(params.get(OAuth2ParameterNames.REDIRECT_URI), "UTF-8")).isEqualTo(redirectUri);
|
||||
assertThat(URLDecoder.decode(params.get(OAuth2ParameterNames.SCOPE), "UTF-8"))
|
||||
.isEqualTo(clientRegistration.getScopes().stream().collect(Collectors.joining(" ")));
|
||||
assertThat(params.get(OAuth2ParameterNames.STATE)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizeClientWhenInvalidClientThenStatusInternalServerError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
clientAnchorElement.setAttribute("href", clientAnchorElement.getHrefAttribute() + "-invalid");
|
||||
|
||||
WebResponse response = null;
|
||||
try {
|
||||
clientAnchorElement.click();
|
||||
} catch (FailingHttpStatusCodeException ex) {
|
||||
response = ex.getResponse();
|
||||
}
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenValidAuthorizationResponseThenDisplayIndexPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
|
||||
WebResponse response = this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
UriComponents authorizeRequestUriComponents = UriComponentsBuilder.fromUri(
|
||||
URI.create(response.getResponseHeaderValue("Location"))).build();
|
||||
|
||||
Map<String, String> params = authorizeRequestUriComponents.getQueryParams().toSingleValueMap();
|
||||
String code = "auth-code";
|
||||
String state = URLDecoder.decode(params.get(OAuth2ParameterNames.STATE), "UTF-8");
|
||||
String redirectUri = URLDecoder.decode(params.get(OAuth2ParameterNames.REDIRECT_URI), "UTF-8");
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2ParameterNames.CODE, code)
|
||||
.queryParam(OAuth2ParameterNames.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
this.assertIndexPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenNoMatchingAuthorizationRequestThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
|
||||
|
||||
String code = "auth-code";
|
||||
String state = "state";
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId();
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2ParameterNames.CODE, code)
|
||||
.queryParam(OAuth2ParameterNames.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
// Clear session cookie will ensure the 'session-saved'
|
||||
// Authorization Request (from previous request) is not found
|
||||
this.webClient.getCookieManager().clearCookies();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("div");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains("authorization_request_not_found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenInvalidStateParamThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
String code = "auth-code";
|
||||
String state = "invalid-state";
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + clientRegistration.getRegistrationId();
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2ParameterNames.CODE, code)
|
||||
.queryParam(OAuth2ParameterNames.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("div");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains("authorization_request_not_found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenMockOAuth2LoginThenIndex() throws Exception {
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||
this.mvc.perform(get("/").with(oauth2Login().clientRegistration(clientRegistration)))
|
||||
.andExpect(model().attribute("userName", "user"))
|
||||
.andExpect(model().attribute("clientName", "GitHub"))
|
||||
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "user")));
|
||||
}
|
||||
|
||||
private void assertLoginPage(HtmlPage page) {
|
||||
assertThat(page.getTitleText()).isEqualTo("Please sign in");
|
||||
|
||||
int expectedClients = 4;
|
||||
|
||||
List<HtmlAnchor> clientAnchorElements = page.getAnchors();
|
||||
assertThat(clientAnchorElements.size()).isEqualTo(expectedClients);
|
||||
|
||||
ClientRegistration googleClientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
|
||||
ClientRegistration githubClientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||
ClientRegistration facebookClientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
|
||||
ClientRegistration oktaClientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
|
||||
|
||||
String baseAuthorizeUri = AUTHORIZATION_BASE_URI + "/";
|
||||
String googleClientAuthorizeUri = baseAuthorizeUri + googleClientRegistration.getRegistrationId();
|
||||
String githubClientAuthorizeUri = baseAuthorizeUri + githubClientRegistration.getRegistrationId();
|
||||
String facebookClientAuthorizeUri = baseAuthorizeUri + facebookClientRegistration.getRegistrationId();
|
||||
String oktaClientAuthorizeUri = baseAuthorizeUri + oktaClientRegistration.getRegistrationId();
|
||||
|
||||
for (int i=0; i<expectedClients; i++) {
|
||||
assertThat(clientAnchorElements.get(i).getAttribute("href")).isIn(
|
||||
googleClientAuthorizeUri, githubClientAuthorizeUri,
|
||||
facebookClientAuthorizeUri, oktaClientAuthorizeUri);
|
||||
assertThat(clientAnchorElements.get(i).asText()).isIn(
|
||||
googleClientRegistration.getClientName(),
|
||||
githubClientRegistration.getClientName(),
|
||||
facebookClientRegistration.getClientName(),
|
||||
oktaClientRegistration.getClientName());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertIndexPage(HtmlPage page) {
|
||||
assertThat(page.getTitleText()).isEqualTo("Spring Security - OAuth 2.0 Login");
|
||||
|
||||
DomNodeList<HtmlElement> divElements = page.getBody().getElementsByTagName("div");
|
||||
assertThat(divElements.get(1).asText()).contains("User: joeg@springsecurity.io");
|
||||
assertThat(divElements.get(4).asText()).contains("You are successfully logged in joeg@springsecurity.io");
|
||||
}
|
||||
|
||||
private HtmlAnchor getClientAnchorElement(HtmlPage page, ClientRegistration clientRegistration) {
|
||||
Optional<HtmlAnchor> clientAnchorElement = page.getAnchors().stream()
|
||||
.filter((e) -> e.asText().equals(clientRegistration.getClientName())).findFirst();
|
||||
|
||||
return (clientAnchorElement.orElse(null));
|
||||
}
|
||||
|
||||
private WebResponse followLinkDisableRedirects(HtmlAnchor anchorElement) throws Exception {
|
||||
WebResponse response = null;
|
||||
try {
|
||||
// Disable the automatic redirection (which will trigger
|
||||
// an exception) so that we can capture the response
|
||||
this.webClient.getOptions().setRedirectEnabled(false);
|
||||
anchorElement.click();
|
||||
} catch (FailingHttpStatusCodeException ex) {
|
||||
response = ex.getResponse();
|
||||
this.webClient.getOptions().setRedirectEnabled(true);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@TestConfiguration
|
||||
public static class SecurityTestConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorizeRequests) ->
|
||||
authorizeRequests
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login((oauth2Login) ->
|
||||
oauth2Login
|
||||
.tokenEndpoint((tokenEndpoint) ->
|
||||
tokenEndpoint
|
||||
.accessTokenResponseClient(this.mockAccessTokenResponseClient())
|
||||
)
|
||||
.userInfoEndpoint((userInfoEndpoint) ->
|
||||
userInfoEndpoint
|
||||
.userService(this.mockUserService())
|
||||
)
|
||||
);
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> mockAccessTokenResponseClient() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
|
||||
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||
.expiresIn(60 * 1000)
|
||||
.build();
|
||||
|
||||
OAuth2AccessTokenResponseClient tokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||
when(tokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
|
||||
return tokenResponseClient;
|
||||
}
|
||||
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> mockUserService() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("id", "joeg");
|
||||
attributes.put("first-name", "Joe");
|
||||
attributes.put("last-name", "Grandja");
|
||||
attributes.put("email", "joeg@springsecurity.io");
|
||||
|
||||
GrantedAuthority authority = new OAuth2UserAuthority(attributes);
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorities.add(authority);
|
||||
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(authorities, attributes, "email");
|
||||
|
||||
OAuth2UserService userService = mock(OAuth2UserService.class);
|
||||
when(userService.loadUser(any())).thenReturn(user);
|
||||
return userService;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2LoginApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OAuth2LoginApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Controller
|
||||
public class OAuth2LoginController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Model model,
|
||||
@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
|
||||
@AuthenticationPrincipal OAuth2User oauth2User) {
|
||||
model.addAttribute("userName", oauth2User.getName());
|
||||
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
|
||||
model.addAttribute("userAttributes", oauth2User.getAttributes());
|
||||
return "index";
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
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:
|
||||
google:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
github:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
facebook:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
okta:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
provider:
|
||||
okta:
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
|
@ -1,34 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||
<head>
|
||||
<title>Spring Security - OAuth 2.0 Login</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
|
||||
<div style="float:left">
|
||||
<span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
|
||||
</div>
|
||||
<div style="float:none"> </div>
|
||||
<div style="float:right">
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>OAuth 2.0 Login with Spring Security</h1>
|
||||
<div>
|
||||
You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>
|
||||
via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>
|
||||
<span style="font-weight:bold">User Attributes:</span>
|
||||
<ul>
|
||||
<li th:each="userAttribute : ${userAttributes}">
|
||||
<span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-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 java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2LoginController}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebMvcTest(OAuth2LoginController.class)
|
||||
public class OAuth2LoginControllerTests {
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void rootWhenAuthenticatedReturnsUserAndClient() throws Exception {
|
||||
this.mvc.perform(get("/").with(oauth2Login()))
|
||||
.andExpect(model().attribute("userName", "user"))
|
||||
.andExpect(model().attribute("clientName", "test"))
|
||||
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "user")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rootWhenOverridingClientRegistrationReturnsAccordingly() throws Exception {
|
||||
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("test")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.clientId("my-client-id")
|
||||
.clientName("my-client-name")
|
||||
.tokenUri("https://token-uri.example.org")
|
||||
.build();
|
||||
|
||||
this.mvc.perform(get("/").with(oauth2Login()
|
||||
.clientRegistration(clientRegistration)
|
||||
.attributes((a) -> a.put("sub", "spring-security"))))
|
||||
.andExpect(model().attribute("userName", "spring-security"))
|
||||
.andExpect(model().attribute("clientName", "my-client-name"))
|
||||
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "spring-security")));
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
= 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. This resource server is configured to accept JWE-encrypted tokens.
|
||||
|
||||
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 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 decrypts the token and subsquently verifies it 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=eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.IyeWtsTonaiWJdoT13B0M7gpqVxAirVGlfqFI4TOmTRcVHICs_ESezS7fa0ODS9XYdwklTtG7hH39yeeMzr2Zo1Ghh-m36fdoqQrV1Do04rUvuTjqbgyNffeZEGB6rquJ-cyAVjp_Oljy10-Bbnw7CeVGwNBSVo9UCB5j49OlNWhLxFpYARFmOlYpXj-s4Q4JiqV6EvjDAYeohAR4QQmND3qoxR-s2I6SLcIho0sSSpUlhrRiqu2uvWefHDcZJdW2WYWnxLHxhzNu3CfnLiqhhaA_YA_iWXR9FYnPDCf_4q3FgSXcgttXzomFKAx5DwnE_dXvsCvpWxslZMU1UIiLA.MHOrrza2GQ9_5PIv.zU4tfhxT6apWBC3stBwQmGlCQBltWVQe4dFIykybWWBFqxo1bf2BZ37twzoEIFXG9jSYEMH4mvBXPWSvn66t-_jnqLnKTJst2plBjhagGCAoLNWXVKeYNp67o-lKOD_JJQFqsRw4oE05VSgRr14MZeaUBFcU3A_kKxMXOu899DKfXBGJvj75H7lDyd8RUXTb-OSWWfUiJc6Y5AUy1zCZCN9yfDsCXt9heTsZANy92Oou9sMFaXkYzyums5OtkBtLFzyuNMEoNioRehTV-FTuL8tDRB1mNhHObwsBfFbR6M1jJK37pHUXGtko-yZ6NGwxyLtwGh4uU2jzE614rQzuzR8aHaHxOkUs1pBTZ8AcRt41snByOe-KU0adthHxedobFiQQBoQ05DgSU7DO6hsK0uVBDF3eG2KjH4L2lZy-WugloLHhdguUoO9F0zUx15-XZO4EVzmhy8xfH2tSXz98eKzz9Dv0DdGnrBL9cK2MM88N1zoq5u4NdlnE12HvuesB7GKdMwZx1-gTw_pzP81TzcctJWl6ETK09Uc.jk0O8qc4Fvip856stDz05Q
|
||||
```
|
||||
|
||||
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=eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.CRBAEgvQhpB6pPQhpkTAKpsDai1FDcvkDSRig1R3OI-g18JdTe-qDhzWwP-hV3aCwFwHxQ_g8Z8OIZvhyKpQaPwBb72UeLqqhzSIkm0gEsmmjYg1vEGOrDH5_Fqlm0LnAnXTmsbOIWYIj11ZuenI2lEmMCkVwqth0RlzakdcHRXiuDTEln_trhZpE2j80X-9rS2gZy9Raa9VLir3-F3wC0GKPEL6e3x1OygC03ix9uyXS3vpTsU9zlgoYADZyaLeOF1mCG4mQhvXs7IPmPbwNsElJwKh0xSQCHvNOQShprlvd3cHiUFKYw9fXphY1O-AUYcRzHk4DjoBdkGNQMy_Kw.KtC_z674rYBtDgkN.e8QU50Iq1JHkn-1USSxpjEkbrukb4cobvlQRK40iXGAKVIuOod4bSq5fDpIAPHugqIf-_zGsvr-2OCOdzhtBikL46wU7UdZppxPWtk-X6kl33zH_XObRMaGfe-hLxt3RPxRVn7I1Hp6tGW1Rkxyf_ESq4XlcbbrkhDoIz_G_LKXJhvQ-xahW2e0AUc7RZSucns4XUeq9xX_dd7Ht-o1TmQI9WFoFc1l7oh9GtQ6GZMsghnZ1VrbIS2L7jSYiSsD2JqSv1LLtOGj_FBA0ufhqM3LloGiwflEwAryMD10oNb73WonKEycEj1rBsTIKW7SHkI-VkrQA4-8N-aLWgHwDnzyPZmyNyKpqUMvhjIE_0w6oqU4HpN7J5nfBEIAtpPZ_pDkwAdxCQ7JV3zfiUnF7ZQ3q1PnSId315si02ZN9-wRSrMHcHnboQN1Hs4xCAfGyClVyLpCzfa_fAehjt6v1DjgjbzwSjr_LdNmWTvXYBhNO8Jq9Vb7axksrdwksD3pYNMY8cRZxP-LO0V5Sv1_kT_Hf2yLo2iTwB8n8szzGrJ4QQLb5Znu7Sv-M2x52cnIDMiorP3LNpFk.G85FuMSm-8bGumFAStiFQA
|
||||
|
||||
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 encrypt using the public key available in the sample.
|
||||
Also it must support JWTs that either use the "scope" or "scp" attribute._
|
||||
|
||||
To change the sample to point at your Authorization Server, simply find this property in the `application.yml`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json
|
||||
```
|
||||
|
||||
And change the property to your Authorization Server's JWK set endpoint:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: https://localhost:9031/pf/JWKS
|
||||
```
|
||||
|
||||
If your Authorization Server does not support RSA_OAEP_256 or AESGCM, then you can change these values in `OAuth2ResourceServerSecurityConfiguration`:
|
||||
|
||||
```java
|
||||
|
||||
```
|
||||
|
||||
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.
|
|
@ -1,13 +0,0 @@
|
|||
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.squareup.okhttp3:mockwebserver'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.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 = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.IyeWtsTonaiWJdoT13B0M7gpqVxAirVGlfqFI4TOmTRcVHICs_ESezS7fa0ODS9XYdwklTtG7hH39yeeMzr2Zo1Ghh-m36fdoqQrV1Do04rUvuTjqbgyNffeZEGB6rquJ-cyAVjp_Oljy10-Bbnw7CeVGwNBSVo9UCB5j49OlNWhLxFpYARFmOlYpXj-s4Q4JiqV6EvjDAYeohAR4QQmND3qoxR-s2I6SLcIho0sSSpUlhrRiqu2uvWefHDcZJdW2WYWnxLHxhzNu3CfnLiqhhaA_YA_iWXR9FYnPDCf_4q3FgSXcgttXzomFKAx5DwnE_dXvsCvpWxslZMU1UIiLA.MHOrrza2GQ9_5PIv.zU4tfhxT6apWBC3stBwQmGlCQBltWVQe4dFIykybWWBFqxo1bf2BZ37twzoEIFXG9jSYEMH4mvBXPWSvn66t-_jnqLnKTJst2plBjhagGCAoLNWXVKeYNp67o-lKOD_JJQFqsRw4oE05VSgRr14MZeaUBFcU3A_kKxMXOu899DKfXBGJvj75H7lDyd8RUXTb-OSWWfUiJc6Y5AUy1zCZCN9yfDsCXt9heTsZANy92Oou9sMFaXkYzyums5OtkBtLFzyuNMEoNioRehTV-FTuL8tDRB1mNhHObwsBfFbR6M1jJK37pHUXGtko-yZ6NGwxyLtwGh4uU2jzE614rQzuzR8aHaHxOkUs1pBTZ8AcRt41snByOe-KU0adthHxedobFiQQBoQ05DgSU7DO6hsK0uVBDF3eG2KjH4L2lZy-WugloLHhdguUoO9F0zUx15-XZO4EVzmhy8xfH2tSXz98eKzz9Dv0DdGnrBL9cK2MM88N1zoq5u4NdlnE12HvuesB7GKdMwZx1-gTw_pzP81TzcctJWl6ETK09Uc.jk0O8qc4Fvip856stDz05Q";
|
||||
String messageReadToken = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.CRBAEgvQhpB6pPQhpkTAKpsDai1FDcvkDSRig1R3OI-g18JdTe-qDhzWwP-hV3aCwFwHxQ_g8Z8OIZvhyKpQaPwBb72UeLqqhzSIkm0gEsmmjYg1vEGOrDH5_Fqlm0LnAnXTmsbOIWYIj11ZuenI2lEmMCkVwqth0RlzakdcHRXiuDTEln_trhZpE2j80X-9rS2gZy9Raa9VLir3-F3wC0GKPEL6e3x1OygC03ix9uyXS3vpTsU9zlgoYADZyaLeOF1mCG4mQhvXs7IPmPbwNsElJwKh0xSQCHvNOQShprlvd3cHiUFKYw9fXphY1O-AUYcRzHk4DjoBdkGNQMy_Kw.KtC_z674rYBtDgkN.e8QU50Iq1JHkn-1USSxpjEkbrukb4cobvlQRK40iXGAKVIuOod4bSq5fDpIAPHugqIf-_zGsvr-2OCOdzhtBikL46wU7UdZppxPWtk-X6kl33zH_XObRMaGfe-hLxt3RPxRVn7I1Hp6tGW1Rkxyf_ESq4XlcbbrkhDoIz_G_LKXJhvQ-xahW2e0AUc7RZSucns4XUeq9xX_dd7Ht-o1TmQI9WFoFc1l7oh9GtQ6GZMsghnZ1VrbIS2L7jSYiSsD2JqSv1LLtOGj_FBA0ufhqM3LloGiwflEwAryMD10oNb73WonKEycEj1rBsTIKW7SHkI-VkrQA4-8N-aLWgHwDnzyPZmyNyKpqUMvhjIE_0w6oqU4HpN7J5nfBEIAtpPZ_pDkwAdxCQ7JV3zfiUnF7ZQ3q1PnSId315si02ZN9-wRSrMHcHnboQN1Hs4xCAfGyClVyLpCzfa_fAehjt6v1DjgjbzwSjr_LdNmWTvXYBhNO8Jq9Vb7axksrdwksD3pYNMY8cRZxP-LO0V5Sv1_kT_Hf2yLo2iTwB8n8szzGrJ4QQLb5Znu7Sv-M2x52cnIDMiorP3LNpFk.G85FuMSm-8bGumFAStiFQA";
|
||||
|
||||
@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!")));
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 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();
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.boot.env;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class MockWebServerPropertySource extends PropertySource<MockWebServer> implements
|
||||
DisposableBean {
|
||||
|
||||
private static final MockResponse JWKS_RESPONSE = response(
|
||||
"{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"i7H90yfquGVhXxekdzXkMaxlIg67Q_ofd7iuFHtgeUx-Iye2QjukuULhl774oITYnZIZsh2UHxRYG8nFypcYZfHJMQes_OYFTkTvRroKll5p3wxSkhpARbkEPFMyMJ5WIm3MeNO2ermMhDWVVeI2xQH-tW6w-C6b5d_F6lrIwCnpZwSv6PQ3kef-rcObp_PZANIo232bvpwyC6uW1W2kpjAvYJhQ8NrkG2oO0ynqEJW2UyoCWRdsT2BLZcFMAcxG3Iw9b9__IbvNoUBwr596JYfzrX0atiKyk4Yg8dJ1wBjHFN2fkHTlzn6HDwTJkj4VNDQvKu4P2zhKn1gmWuxhuQ\"}]}",
|
||||
200
|
||||
);
|
||||
|
||||
private static final MockResponse NOT_FOUND_RESPONSE = response(
|
||||
"{ \"message\" : \"This mock authorization server responds to just one request: GET /.well-known/jwks.json.\" }",
|
||||
404
|
||||
);
|
||||
|
||||
/**
|
||||
* Name of the random {@link PropertySource}.
|
||||
*/
|
||||
public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver";
|
||||
|
||||
private static final String NAME = "mockwebserver.url";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class);
|
||||
|
||||
private boolean started;
|
||||
|
||||
public MockWebServerPropertySource() {
|
||||
super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new MockWebServer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
if (!name.equals(NAME)) {
|
||||
return null;
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Looking up the url for '" + name + "'");
|
||||
}
|
||||
String url = getUrl();
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
getSource().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the URL (i.e. "http://localhost:123456")
|
||||
* @return
|
||||
*/
|
||||
private String getUrl() {
|
||||
MockWebServer mockWebServer = getSource();
|
||||
if (!this.started) {
|
||||
intializeMockWebServer(mockWebServer);
|
||||
}
|
||||
String url = mockWebServer.url("").url().toExternalForm();
|
||||
return url.substring(0, url.length() - 1);
|
||||
}
|
||||
|
||||
private void intializeMockWebServer(MockWebServer mockWebServer) {
|
||||
Dispatcher dispatcher = new Dispatcher() {
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) {
|
||||
if ("/.well-known/jwks.json".equals(request.getPath())) {
|
||||
return JWKS_RESPONSE;
|
||||
}
|
||||
|
||||
return NOT_FOUND_RESPONSE;
|
||||
}
|
||||
};
|
||||
|
||||
mockWebServer.setDispatcher(dispatcher);
|
||||
try {
|
||||
mockWebServer.start();
|
||||
this.started = true;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not start " + mockWebServer, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static MockResponse response(String body, int status) {
|
||||
return new MockResponse()
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.setResponseCode(status)
|
||||
.setBody(body);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This provides integration of a {@link okhttp3.mockwebserver.MockWebServer} and the
|
||||
* {@link org.springframework.core.env.Environment}
|
||||
* @author Rob Winch
|
||||
*/
|
||||
package org.springframework.boot.env;
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 Josh Cummings
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2ResourceServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OAuth2ResourceServerApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RestController
|
||||
public class OAuth2ResourceServerController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(@AuthenticationPrincipal Jwt jwt) {
|
||||
return String.format("Hello, %s!", jwt.getSubject());
|
||||
}
|
||||
|
||||
@GetMapping("/message")
|
||||
public String message() {
|
||||
return "secret message";
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 java.net.URL;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
|
||||
import com.nimbusds.jose.EncryptionMethod;
|
||||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.KeyUse;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
|
||||
import com.nimbusds.jose.proc.JWEDecryptionKeySelector;
|
||||
import com.nimbusds.jose.proc.JWEKeySelector;
|
||||
import com.nimbusds.jose.proc.JWSKeySelector;
|
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
import com.nimbusds.jwt.proc.JWTProcessor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
|
||||
private final JWEAlgorithm jweAlgorithm = JWEAlgorithm.RSA_OAEP_256;
|
||||
private final EncryptionMethod encryptionMethod = EncryptionMethod.A256GCM;
|
||||
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
|
||||
URL jwkSetUri;
|
||||
|
||||
@Value("${sample.jwe-key-value}")
|
||||
RSAPrivateKey key;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authorizeRequests) ->
|
||||
authorizeRequests
|
||||
.antMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer((oauth2ResourceServer) ->
|
||||
oauth2ResourceServer
|
||||
.jwt(withDefaults())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
return new NimbusJwtDecoder(jwtProcessor());
|
||||
}
|
||||
|
||||
private JWTProcessor<SecurityContext> jwtProcessor() {
|
||||
JWKSource<SecurityContext> jwsJwkSource = new RemoteJWKSet<>(this.jwkSetUri);
|
||||
JWSKeySelector<SecurityContext> jwsKeySelector =
|
||||
new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwsJwkSource);
|
||||
|
||||
JWKSource<SecurityContext> jweJwkSource = new ImmutableJWKSet<>(new JWKSet(rsaKey()));
|
||||
JWEKeySelector<SecurityContext> jweKeySelector =
|
||||
new JWEDecryptionKeySelector<>(this.jweAlgorithm, this.encryptionMethod, jweJwkSource);
|
||||
|
||||
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
||||
jwtProcessor.setJWEKeySelector(jweKeySelector);
|
||||
|
||||
return jwtProcessor;
|
||||
}
|
||||
|
||||
private RSAKey rsaKey() {
|
||||
RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) this.key;
|
||||
Base64URL n = Base64URL.encode(crtKey.getModulus());
|
||||
Base64URL e = Base64URL.encode(crtKey.getPublicExponent());
|
||||
return new RSAKey.Builder(n, e)
|
||||
.privateKey(this.key)
|
||||
.keyUse(KeyUse.ENCRYPTION)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
org.springframework.boot.env.EnvironmentPostProcessor=org.springframework.boot.env.MockWebServerEnvironmentPostProcessor
|
|
@ -1,9 +0,0 @@
|
|||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json
|
||||
|
||||
sample:
|
||||
jwe-key-value: classpath:simple.priv
|
|
@ -1,28 +0,0 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA
|
||||
iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM
|
||||
g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK
|
||||
LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF
|
||||
oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc
|
||||
3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn
|
||||
+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE
|
||||
E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek
|
||||
lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG
|
||||
mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7
|
||||
62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0
|
||||
bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA
|
||||
+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH
|
||||
Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA
|
||||
8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd
|
||||
I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY
|
||||
QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d
|
||||
rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk
|
||||
HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA
|
||||
Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN
|
||||
HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a
|
||||
FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF
|
||||
snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H
|
||||
c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM
|
||||
TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR
|
||||
47jndeyIaMTNETEmOnms+as17g==
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,9 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd
|
||||
7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv
|
||||
c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6
|
||||
iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2
|
||||
kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o
|
||||
RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj
|
||||
KwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue