はじめに
Spring Bootを使ったWebアプリケーション開発では、セキュリティ対策も重要なポイント。その代表的なセキュリティフレームワークが「Spring Security」です。
Spring Securityについて調べても古い記事が多くエラーを大量に出してしまったりして苦労したので、自信の知識定着のため、今後のために自分が書いたコードを一部抜粋してコードを掲載します。他にもっといい書き方があればかえていきたいものです
本記事では、Spring Securityの基本的な考え方と実際のログイン処理のコード例を紹介します。
💡 Spring Securityとは?
Spring Securityは、Springアプリケーションにセキュリティ機能(認証・認可)を組み込むためのフレームワークです。
主な機能
- 認証(ログイン機能)
- 認可(アクセス制御)
- パスワードの暗号化
- セッション管理
- CSRF対策
- CORS対応 など
🔑 認証(Authentication)
「このユーザーが誰なのか」を確認する仕組み。
ログインフォームで入力されたユーザー名とパスワードを検証して、本人であることを証明します。
例:
user@example.comとパスワードが正しい場合にログイン成功。
Spring Securityでは UserDetailsService や AuthenticationProvider がこの処理を担当します。
🧭 認可(Authorization)
「このユーザーが何をできるのか」を制御する仕組み。
ユーザーのロール(権限)に基づいてアクセスできるURLや機能を制限します。
例:
- 管理者(
ROLE_ADMIN)は/admin/**にアクセス可能- 一般ユーザー(
ROLE_USER)は/user/**のみ許可
SecurityConfig クラスで requestMatchers() や hasRole() を使って制御します。
🛡️ CSRF(Cross-Site Request Forgery)対策
「他サイトからの不正なリクエスト送信」を防ぐ仕組み。
悪意あるサイトが、ログイン済みユーザーのセッションを悪用して操作を行う攻撃を防止します。
- Spring Securityではフォーム送信時に自動でCSRFトークンを付与します。
- REST APIなどセッションを使わない通信では、
csrf().disable()で無効化するのが一般的です。
例:
- 通常のHTMLフォーム → 有効(CSRFトークン付き)
- REST API(フロントとバックが分離)→ 無効化推奨
🌐 CORS(Cross-Origin Resource Sharing)
「異なるドメイン間の通信を許可する設定」。
フロントエンド(例:ReactやVue)がバックエンドAPI(Spring Boot)を呼び出す際に必要になります。
- デフォルトでは異なるオリジンの通信はブロックされます。
- Spring Securityでは
cors()を有効化し、CorsConfigurationSourceで許可設定を行います。
例:
- フロント:
http://localhost:3000(React)- バック:
http://localhost:8080/api(Spring)
→corsConfiguration.addAllowedOrigin("http://localhost:3000");で許可
🧩 単語まとめ
| 項目 | 内容 | 設定場所 |
|---|---|---|
| 認証 | ユーザーの本人確認 |
UserDetailsService, AuthenticationProvider
|
| 認可 | 権限に応じたアクセス制御 |
SecurityConfig(authorizeHttpRequests()) |
| CSRF | 不正リクエスト防止 | http.csrf() |
| CORS | 異なるドメイン間通信の許可 |
http.cors() + CorsConfigurationSource
|
🔧 実装準備
1. 依存関係の追加(build.gradle)
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'
}
2. エンティティクラス(mUser)
@Entity
public class mUser {
private Integer usersid;
private String username;
private String password;
private String role;
// getter/setter省略
}
3. カスタムユーザークラス(CustomUserDetails)
@Entity
public class CustomUserDetails implements UserDetails {
/** DB から取得したユーザーエンティティ */
private final MUser mUser;
/** 権限コレクション */
private final Collection<? extends GrantedAuthority> authorities;
public CustomUserDetails(MUser mUser) {
this.mUser = mUser;
// ROLE_ 付きでそのまま権限に
this.authorities = List.of(new SimpleGrantedAuthority(mUser.getRole()));
}
/* ─────────── 追加で使いたい独自 getter ─────────── */
/** ユーザー SID を返す */
public Integer getUserSid() {
return mUser.getUserSid();
}
/** 表示用ユーザー名 */
public String getDisplayName() {
return mUser.getUserName();
}
/** MUser をそのまま欲しい場合 */
public MUser getMUser() {
return mUser;
}
/* ─────────── UserDetails 実装 ─────────── */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return mUser.getPassword();
}
/** Spring Security がログイン ID とみなす値 */
@Override
public String getUsername() {
return mUser.getUserId();
}
@Override
public boolean isAccountNonExpired() {
return true; // 期限切れ判定を実装するならここで
}
@Override
public boolean isAccountNonLocked() {
return true; // ロック判定を実装するならここで
}
@Override
public boolean isCredentialsNonExpired() {
return true; // 資格情報の期限切れ判定を実装するならここで
}
@Override
public boolean isEnabled() {
return true; // 無効ユーザー判定を実装するならここで
}
}
4.🔐 Spring Securityの設定クラス(SecurityConfig)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
@Configuration // Springの設定クラスとして認識させる
@EnableWebSecurity // Spring SecurityのWebセキュリティ機能を有効化
@RequiredArgsConstructor // final フィールド や @NonNull が付いたフィールド に対して コンストラクタを自動生成
public class SecurityConfig {
// ユーザー情報を取得する独自のUserDetailsService
@Autowired
private CustomUserDetailsService userDetailsService;
// セキュリティフィルターチェーンの設定
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// リクエストごとのアクセス制御ルールを定義
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/signup/**", "/css/**", "/js/**").permitAll() // 認証不要のパス
.anyRequest().authenticated() // それ以外は認証が必要
)
// フォームログインの設定
.formLogin(login -> login
.loginPage("/login") // ログインページのURL
.defaultSuccessUrl("/dashboard", true) // ログイン成功後の遷移先
.failureUrl("/login?error") // ログイン失敗時の遷移先
.permitAll() // 認証なしでアクセス可能
)
// ログアウトの設定
.logout(logout -> logout
.logoutUrl("/logout") // ログアウト処理のURL
.logoutSuccessUrl("/login?logout") // ログアウト後の遷移先
.invalidateHttpSession(true) // セッションを無効化
.deleteCookies("JSESSIONID") // クッキーを削除
.permitAll() // 認証なしでアクセス可能
)
// 認証プロバイダーの設定
.authenticationProvider(authenticationProvider());
// セキュリティフィルターを構築して返却
return http.build();
}
// DaoAuthenticationProviderのBean定義(ユーザー情報とパスワードの照合用)
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService((UserDetailsService) userDetailsService); // ユーザー情報取得ロジックを設定
provider.setPasswordEncoder(passwordEncoder()); // パスワードのエンコーダーを設定
return provider;
}
// パスワードをハッシュ化するためのエンコーダー(BCrypt)
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
🔧 SecurityConfigでよく使用されるメソッド一覧と詳細解説
Spring Securityの設定は、HttpSecurity オブジェクトを通して行われます。
以下では、SecurityFilterChain の中で使用できる主なメソッドと、その役割をまとめています。
🛡 認可関連(URLごとのアクセス制御)
| メソッド | 説明 | 使用例 |
|---|---|---|
authorizeHttpRequests() |
URLごとのアクセス権限を設定するメインメソッド。これ以降で個別設定を行う。 | .authorizeHttpRequests(auth -> auth.requestMatchers("/admin/**").hasRole("ADMIN")) |
requestMatchers("/path/**") |
特定のURLパターンを指定してアクセス制御を設定。 | .requestMatchers("/api/**").authenticated() |
permitAll() |
すべてのユーザーにアクセスを許可する。 | .requestMatchers("/login").permitAll() |
denyAll() |
すべてのユーザーにアクセスを拒否する。 | .requestMatchers("/test").denyAll() |
hasRole("ROLE") |
特定のロール(権限)を持つユーザーのみアクセス許可。 ※Spring側では "ROLE_ADMIN"のように自動で接頭辞が付く。 |
.requestMatchers("/admin/**").hasRole("ADMIN") |
hasAnyRole("A","B") |
複数のロールのいずれかを持つ場合にアクセス許可。 | .requestMatchers("/staff/**").hasAnyRole("STAFF","ADMIN") |
hasAuthority("AUTH") |
特定の権限を持つユーザーのみ許可(hasRoleより低レベルの制御)。 |
.requestMatchers("/manage").hasAuthority("MANAGE_PRIV") |
hasAnyAuthority("A","B") |
複数の権限のいずれかを持つ場合に許可。 | .requestMatchers("/edit").hasAnyAuthority("WRITE","ADMIN") |
authenticated() |
ログイン済みユーザーのみアクセス許可。 | .anyRequest().authenticated() |
anonymous() |
未ログインユーザーのみアクセス許可。 | .requestMatchers("/register").anonymous() |
anyRequest() |
定義されていないすべてのリクエストに対する設定を指定。 | .anyRequest().authenticated() |
🔑 認証(ログイン関連設定)
| メソッド | 説明 | 使用例 |
|---|---|---|
formLogin() |
フォームログイン機能を有効化。 ログインページ・成功時URLなどを設定。 |
.formLogin(Customizer.withDefaults()) |
loginPage("/login") |
カスタムログインページのURLを指定。 | .formLogin(login -> login.loginPage("/login")) |
loginProcessingUrl("/auth") |
認証リクエストを受け取るURL(formのaction先)を指定。 | .formLogin(login -> login.loginProcessingUrl("/auth")) |
defaultSuccessUrl("/home", true) |
ログイン成功時に遷移するURLを指定。trueを指定すると常にこのURLにリダイレクト。 |
.formLogin(login -> login.defaultSuccessUrl("/home", true)) |
failureUrl("/login?error") |
認証失敗時に遷移するURLを指定。 | .formLogin(login -> login.failureUrl("/login?error")) |
permitAll() |
ログインページへのアクセスを全ユーザーに許可。 | .formLogin(login -> login.permitAll()) |
🚪 ログアウト関連設定
| メソッド | 説明 | 使用例 |
|---|---|---|
logout() |
ログアウト機能を有効化。 | .logout(Customizer.withDefaults()) |
logoutUrl("/logout") |
ログアウト処理のURLを指定。 | .logout(logout -> logout.logoutUrl("/logout")) |
logoutSuccessUrl("/login?logout") |
ログアウト後に遷移するURL。 | .logout(logout -> logout.logoutSuccessUrl("/login?logout")) |
invalidateHttpSession(true) |
ログアウト時にセッションを無効化。 | .logout(logout -> logout.invalidateHttpSession(true)) |
deleteCookies("JSESSIONID") |
クッキー削除を設定(セッション残留防止)。 | .logout(logout -> logout.deleteCookies("JSESSIONID")) |
🔒 CSRF・CORS・ヘッダー関連
| メソッド | 説明 | 使用例 |
|---|---|---|
csrf().disable() |
CSRF保護を無効化。 REST APIなど、トークン認証を使う場合によく使用。 |
.csrf(csrf -> csrf.disable()) |
cors() |
クロスオリジンリクエストを許可(CORS設定を有効化)。 フロント(例:ReactやVue)からのAPI呼び出し時に必須。 |
.cors(Customizer.withDefaults()) |
headers().frameOptions().disable() |
X-Frame-Optionsを無効化。 主にH2コンソール利用時などiframeを使う画面で必要。 |
.headers(headers -> headers.frameOptions().disable()) |
🧠 Remember-Me(ログイン状態保持)
| メソッド | 説明 | 使用例 |
|---|---|---|
rememberMe() |
ログイン状態をCookieで保持する機能を有効化。 | .rememberMe(Customizer.withDefaults()) |
tokenValiditySeconds(604800) |
有効期限(秒)を設定(例:7日間)。 | .rememberMe(r -> r.tokenValiditySeconds(7 * 24 * 60 * 60)) |
key("uniqueAndSecret") |
トークンの暗号化キーを指定。 | .rememberMe(r -> r.key("mySecret")) |
👥 セッション管理関連
| メソッド | 説明 | 使用例 |
|---|---|---|
sessionManagement() |
セッション関連の設定を開始。 | .sessionManagement(Customizer.withDefaults()) |
sessionCreationPolicy(SessionCreationPolicy.STATELESS) |
セッションを使用しない(JWT認証など)。 | .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) |
invalidSessionUrl("/login?expired") |
セッション切れ時の遷移先を指定。 | .sessionManagement(s -> s.invalidSessionUrl("/login?expired")) |
🔐 PasswordEncoder(パスワード暗号化)
Spring Securityでは平文パスワードを使用せず、必ずエンコードします。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
5.🧩 ユーザー情報を取得する独自サービス(UserDetailsService)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.jp.wc.mybatis.login.CustomUserMapper;
import com.jp.wc.mybatis.model.MUser;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private CustomUserMapper customUserMapper;
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
// ログイン情報をDBから取得
MUser user = customUserMapper.selectByUserId(userId);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user);
}
}
6.🧩 ログイン画面(Thymeleaf)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ログイン</title>
<!-- Bootstrap CSS CDN -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384‑JcKb8q3iqJ61gNVpCF6e1QZr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous">
</head>
<body class="bg-light">
<div class="container vh-100 d-flex justify-content-center align-items-center">
<div class="card shadow-sm w-100" style="max-width: 400px;">
<div class="card-body">
<h3 class="card-title text-center mb-4">ログイン</h3>
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="username">ユーザー名</label>
<input type="text" class="form-control" id="username" name="username"
placeholder="Enter username" required />
</div>
<div class="form-group">
<label for="password">パスワード</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Enter password" required />
</div>
<button type="submit" class="btn btn-primary btn-block">ログイン</button>
</form>
</div>
</div>
</div>
</body>
</html>
7.🏠 ログイン後の画面(Controller)
@Controller
public class testController {
@GetMapping("/dashboard")
public String dashboard() {
return "dashboard";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
おわりに
Spring Securityを使えば、ログイン機能を比較的簡単に実装できます。
ただし、設定やカスタマイズには慣れが必要なので、今回のような基本構成から少しずつ手を加えていくのがおすすめです。