10 changed files with 301 additions and 0 deletions
@ -0,0 +1,41 @@
@@ -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<Test> { |
||||
useJUnitPlatform() |
||||
} |
||||
|
||||
tasks.withType<KotlinCompile> { |
||||
kotlinOptions { |
||||
freeCompilerArgs = listOf("-Xjsr305=strict") |
||||
jvmTarget = "1.8" |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -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<String>) { |
||||
runApplication<KotlinWebfluxApplication>(*args) |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -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) |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -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" |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
server: |
||||
port: 8080 |
||||
|
||||
spring: |
||||
thymeleaf: |
||||
cache: false |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
body { |
||||
font-family: sans; |
||||
font-size: 1em; |
||||
} |
||||
|
||||
div.logout { |
||||
float: right; |
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
<!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> |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
<!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> |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
<!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> |
||||
@ -0,0 +1,67 @@
@@ -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 |
||||
} |
||||
} |
||||
Loading…
Reference in new issue