Spring Boot(Spring Security)を使って、簡単なログインフォームを作成し、認証機能を実装する方法をシンプルにまとめました。初心者の方でも試しやすい内容になっています!
データーベースを使った認証方法の解説はコチラをみてください。
前提条件
- Kotlinを使用
- Spring Boot 3.4.1を使ったプロジェクト
必要な依存関係
Spring Boot Initializerで、以下の依存関係を追加してください:
- Spring Web
- Spring Security
- Thymeleaf
- Spring Boot DevTools(必要なら)
build.gradle.kts
に以下のような依存関係が含まれているはずです:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
developmentOnly("org.springframework.boot:spring-boot-devtools")
}
セキュリティ設定クラスの作成
次に、ログイン周りの設定をするためのクラスを作ります。このクラスでは、どのページにアクセスを許可するか、ログインページの設定、ログイン後の遷移先など、ログイン処理全体の詳細を決定します。
コード例
package com.example.securityconfig
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.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.SecurityFilterChain
@Configuration
class SecurityConfig {
// パスワードをハッシュ化するためのエンコーダーをBeanとして定義
@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
// ユーザー情報をメモリ上に定義するサービスを設定
@Bean
fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService {
val user = User.builder()
.username("user") // ユーザー名を設定
.password(passwordEncoder.encode("password")) // パスワードをハッシュ化して設定
.roles("USER") // ユーザーに付与するロールを設定
.build()
return InMemoryUserDetailsManager(user) // メモリ上のユーザー管理サービスを返す
}
// セキュリティ設定の詳細を定義するBean
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { auth ->
auth.requestMatchers("/login").permitAll() // ログインページは誰でもアクセス可能
.anyRequest().authenticated() // その他のページは認証が必要
}
http.formLogin { form ->
form.loginPage("/login") // ログインページを設定
.failureUrl("/login?error") // ログイン失敗時のリダイレクト先
.defaultSuccessUrl("/home", true) // ログイン成功時のリダイレクト先
.permitAll() // ログインページへのアクセスを許可
}
http.logout { logout ->
logout.logoutUrl("/logout") // ログアウト時のURLを設定
.logoutSuccessUrl("/login") // ログアウト成功後のリダイレクト先
.permitAll() // ログアウトへのアクセスを許可
}
return http.build() // 設定を反映
}
}
ログインフォーム、ホーム画面の作成
次に、ログイン画面(login.html
)とホーム画面(home.html
)を作ります。
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username">
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password">
</div>
<button type="submit">Login</button>
<div th:if="${param.error}">
ユーザー名またはパスワードが間違っています。
</div>
</form>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>ようこそ!</h1>
<form th:action="@{/logout}" method="post">
<button type="submit">ログアウト</button>
</form>
</body>
</html>
コントローラーの作成
最後に、ページを表示するためのコントローラーを作ります。
package com.example.securityconfig
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
@Controller
class LoginController {
// ログインページを表示するエンドポイント
@GetMapping("/login")
fun login(): String = "login" // login.htmlを返す
// ホームページを表示するエンドポイント
@GetMapping("/home")
fun home(): String = "home" // home.htmlを返す
}
動作確認
プロジェクトを起動して、ブラウザでhttp://localhost:8080
にアクセスします。
ログインフォームが表示されたら、以下の情報を使ってログインしてください:
- ユーザー名:
user
- パスワード:
password
ログイン成功後、ホーム画面に移動します。
ユーザー名、パスワードの組み合わせが間違っている場合は、「ユーザー名またはパスワードが間違っています。」と表示されるはずです。
意外なハマりポイント
Spring Securityには、CSRF(クロスサイトリクエストフォージェリ)攻撃対策のための機能がデフォルトで有効になっています。
CSRFは認証されたユーザーが意図せずに悪意のあるリクエストを送信してしまう攻撃手法です。
CSRF攻撃について詳しい解説は以下のリンクを参考にしてください:
POST、PUT、DELETEでパラメーターを送信するとき、CSRFトークンを送信しないと自動的にSpring Securityが403のステータスエラーを出力します。
ブラウザの「ソースコードを表示」等を使って、HTMLを覗いてみると、身に覚えのないname="_csrf”
のinputが追加されているはずです。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post"><input type="hidden" name="_csrf" value="-jL4Tq__qMcGSm1A08Jr9H0cNaKAk93Wd72zQZuQiKbXdN8EygCcL5qezP4rKAt1te9fxRh6GJvkq-j7QYqCcKry7pW0Tbpg"/>
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username">
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password">
</div>
<button type="submit">Login</button>
</form>
</body>
</html>
このname=“_csrf”
がない場合は、<form th:action="@{/login}" method="post”>
のth
が抜けてないか、method=post
が抜けてないか確認してください。
とりあえずサンプルでログイン認証処理を動かしたいなら、CSRF攻撃対策の機能を無効化することが出来ます。
// セキュリティ設定の詳細を定義するBean
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// CSRF攻撃対策のトークンを無効化
httpSecurity.csrf { csrf -> csrf.disable() }
http.authorizeHttpRequests { auth ->
auth.requestMatchers("/login").permitAll() // ログインページは誰でもアクセス可能
.anyRequest().authenticated() // その他のページは認証が必要
}
http.formLogin { form ->
form.loginPage("/login") // カスタムログインページを設定
.failureUrl("/login?error") // ログイン失敗時のリダイレクト先
.defaultSuccessUrl("/home", true) // ログイン成功時のリダイレクト先
.permitAll() // ログインページへのアクセスを許可
}
http.logout { logout ->
logout.logoutUrl("/logout") // ログアウト時のURLを設定
.logoutSuccessUrl("/login") // ログアウト成功後のリダイレクト先
.permitAll() // ログアウトへのアクセスを許可
}
return http.build() // 設定を反映
}
おわりに
以上で、Spring Securityを使った簡単なログイン処理が完成です!この仕組みを元にして、次はデータベースと連携して動的なユーザー管理を実現したり、JWT認証を活用してAPIを安全に保護する仕組みについて解説していく予定です。
Spring Bootで、データベースを使ったログイン認証の解説はコチラ