diff --git a/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts b/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts new file mode 100644 index 0000000000..20eb7b4060 --- /dev/null +++ b/samples/boot/kotlin-webflux/spring-security-samples-boot-kotlin-webflux.gradle.kts @@ -0,0 +1,41 @@ +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 { + useJUnitPlatform() +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = "1.8" + } +} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt new file mode 100644 index 0000000000..572be2a6e3 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/KotlinWebfluxApplication.kt @@ -0,0 +1,27 @@ +/* + * 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) { + runApplication(*args) +} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt new file mode 100644 index 0000000000..e3193c24c6 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt @@ -0,0 +1,56 @@ +/* + * 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.Customizer +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) + } +} diff --git a/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt new file mode 100644 index 0000000000..991c0195c0 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/kotlin/org/springframework/security/samples/web/MainController.kt @@ -0,0 +1,39 @@ +/* + * 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" + } +} diff --git a/samples/boot/kotlin-webflux/src/main/resources/application.yml b/samples/boot/kotlin-webflux/src/main/resources/application.yml new file mode 100644 index 0000000000..8c01e005bc --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8080 + +spring: + thymeleaf: + cache: false diff --git a/samples/boot/kotlin-webflux/src/main/resources/css/main.css b/samples/boot/kotlin-webflux/src/main/resources/css/main.css new file mode 100644 index 0000000000..de0941ecd5 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/resources/css/main.css @@ -0,0 +1,8 @@ +body { + font-family: sans; + font-size: 1em; +} + +div.logout { + float: right; +} diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/index.html b/samples/boot/kotlin-webflux/src/main/resources/templates/index.html new file mode 100644 index 0000000000..f637854f04 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/resources/templates/index.html @@ -0,0 +1,24 @@ + + + + Hello Spring Security + + + + +
+ Logged in user: | + Roles: +
+
+ +
+
+
+

Hello Spring Security

+

This is an unsecured page, but you can access the secured pages after authenticating.

+ + + diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/login.html b/samples/boot/kotlin-webflux/src/main/resources/templates/login.html new file mode 100644 index 0000000000..2ee9216937 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/resources/templates/login.html @@ -0,0 +1,20 @@ + + + + Login page + + + + +

Login page

+

Example user: user / password

+
+ : +
+ : +
+ +
+

Back to home page

+ + diff --git a/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html b/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html new file mode 100644 index 0000000000..393f6d3705 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/main/resources/templates/user/index.html @@ -0,0 +1,13 @@ + + + + Hello Spring Security + + + + +
+

This is a secured page!

+

Back to home page

+ + diff --git a/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt b/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt new file mode 100644 index 0000000000..fc126e58b7 --- /dev/null +++ b/samples/boot/kotlin-webflux/src/test/kotlin/org/springframework/security/samples/KotlinWebfluxApplicationTests.kt @@ -0,0 +1,67 @@ +/* + * 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 + } +}