spring-security v6+KotlinでHelloWorld
はじめに
本記事ではspring-security
を使ったハンズオンを行います
spring-securityは2022年12月20日にversion 6.0へと変わり、いままでの設定ファイルと大きく変わることになりました。
筆者はKotlinの学習をはじめた2023年1月、ネットにほとんど情報がない中で試行錯誤したので、今回は簡単なチュートリアルという形でv6の設定ファイルについて紹介していきます
spring-securityの学習
spring-securityがそもそもなんなのか、どのように使えるのかなどは設定ファイルが古くはなりますが以下のサイトが参考になります
また学習を進める上で以下の書籍は大変参考になりました。まずはこの書籍で概要を掴んでから実装を勧めてみるのがおすすめです
開発環境
- IntelliJ IDEA Community Edition
- open jdk version 18
プロジェクト作成
まずはSpringBootのプロジェクト作成です
こちらのサイトにアクセスして以下の設定をします
項目 | 値 |
---|---|
Project | Gradle-Kotlin |
Language | Kotlin |
Artifact | security |
Name | security |
そして依存関係を追加します
「ADD DEPENDENCIES」をクリックして、「Spring Web」、「Spring Security」を追加します
「GENERATOR」をクリックしてダウンロードをします
適当な場所に解凍して、IntelliJでフォルダを開きます
APIの準備
今回は2つのAPIを用意します
まずは/src/main/kotlin/com/example/security
にToDoController.kt
を作成して以下にします
package com.example.security
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class ToDoController() {
@GetMapping("/hello")
fun hello(): String {
return "Hello"
}
@GetMapping("/todo")
fun todo(): String {
return "todo"
}
}
注意としては今回はAPIなので@RestController
でアノテーションしているところです
@Controller
にすると思わぬエラーが出ますので注意しましょう
今回はAPIを以下のように使い分けます
/hello
: 認証関係なく誰でもアクセスできる
/todo
: ログインしたユーザーのみアクセスできる
spring-securityの設定
次にspring-securityの設定を行います
現在以下のコマンドを叩くと401エラー
が発生します
$ curl localhost:8080/hello
これはspring-securityを依存関係に追加しているのでデフォルトですべてのパスはログイン認証をしていないとアクセスできないようになっているからです
まずは/hello
のみログイン認証なしでアクセスできるように設定していきましょう
/helloを認証無しでアクセスする設定
/src/main/kotlin/com/example/security
にSecurityConfig.kt
を作成して以下にします
ここに設定を書いていきます
package com.example.security
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun configureHttpSecurity(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity
.authorizeHttpRequests()
.requestMatchers("/hello", "/login").permitAll()
.anyRequest().authenticated()
return httpSecurity.build()
}
}
APIを再起動してアクセスしてみます
$ curl localhost:8080/hello
するとHello
と返ってきます
簡単にそれぞれについて解説していきます
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun configureHttpSecurity(httpSecurity: HttpSecurity): SecurityFilterChain {
ここに認証に関する設定をかく
}
}
ここは雛形です。configureHttpSecurity
の中にどのアクセスが認証が必要かを設定できます
以前はWebSecurityConfigAdapter
を使った実装でしたが変わっています
.authorizeHttpRequests()
.requestMatchers("/hello", "/login").permitAll()
.anyRequest().authenticated()
ここでは/hello
と/login
のアクセスに対してpermitAll()
をすることで認証なしにアクセスできるようにしました
/login
は先ほど作成していませんが、これはspring-securityがログイン用に裏で用意してくれているAPIです
そしてanyRequest().authenticated()
でそれ以外のパスのアクセスは認証が必要と設定しました
ためしにcurl http://localhost:8080/todo
にアクセスすると401エラー
が返ってきます
$ curl -i http://localhost:8080/todo # 401エラー
/todoにアクセスする
では認証を行えるように設定を追加して、/todo
にアクセスできるようにします
package com.example.security
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun configureHttpSecurity(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity
.authorizeHttpRequests()
.requestMatchers("/hello", "/login").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
return httpSecurity.build()
}
@Bean
fun users(): UserDetailsService {
val user = User.builder()
.username("user")
.password(passwordEncoder()?.encode("password"))
.roles("USER")
.build()
return InMemoryUserDetailsManager(user)
}
@Bean
fun passwordEncoder(): PasswordEncoder? {
return BCryptPasswordEncoder()
}
}
追加した箇所を説明します
.csrf().disable()
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
まずcsrf().disable()
でCSRFトークンによる証明を無効にしています
そして、loginProcessingUrl
でログインフォームを有効化して、ユーザーのパラメータはemail
、パスワードのパラメータはpassword
で受け付けるように設定しました
POSTリクエストでemail
とpassword
の2つを送り、それらを用いてログイン認証を行うことになります
@Bean
fun users(): UserDetailsService {
# 認証設定を書く
}
ここで認証の具体的な内容を設定します
ここも昔であればconfigure
として実装していたので変わった箇所になります
val user = User.builder()
.username("user")
.password(passwordEncoder()?.encode("password"))
.roles("USER")
.build()
ここでは認証ユーザーを作成しています
ユーザー名: user
パスワード: password
というログイン認証があった場合にUSER
というロールを付与してログインできるようにしています
このように事前にログインできるアカウントを定義しておくのをインメモリ認証
とよびます
@Bean
fun passwordEncoder(): PasswordEncoder? {
return BCryptPasswordEncoder()
}
APIに渡されたパスワードは自動的に暗号化されるため、比較するパスワードも暗号化しておく必要があるのでこの関数を用意しました
では、実際に認証して/todo
にアクセスできるか確かめます
$ curl -i -c cookie.txt -H 'Content-Type:application/x-www-form-urlencoded' -X POST -d 'email=user' -d 'password=password' http://localhost:8080/login
このコマンドでまず/login
で認証を行い、認証情報をcookie.txt
としてファイルに保存しています
$ cat cookie.txt
# Netscape HTTP Cookie File
# https://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 JSESSIONID BF71008CD281961A6463394AA8422306
このように情報が乗っています
次にこのファイルを利用して/todo
にアクセスします
$ curl -b cookie.txt http://localhost:8080/todo
するとtodo
というレスポンスが返ってきて認証してアクセスできるようになったことが確認できました
おわりに
私自身KotlinもSpringBootも利用したことがなかったのでここまでやるのにもかなり苦戦しました
このようなハンズオンがあればいいなと思っていろいろ情報を集めていましたが、体系的なものが見つからなかったので作成してみました
Discussion