Spring Bootの認証をSpring Securityで実装する方法【Spring Security 6/7対応・2026年版】
Spring Bootで認証機能を作ろうとして、参考にしたコードがコンパイルすら通らない——2026年時点でよく起きる状況だ。原因の多くは、記事で使われている WebSecurityConfigurerAdapter や authorizeRequests() が Spring Security 6 で削除されている点にある。本記事は、現行の Spring Boot 3.5/4.1(それぞれ Spring Security 6.5/7.1)で実際に動く書き方に絞り、フォームログイン・データベース認証・SPA向けのJWT認証までを実装コード付きで解説する。
まとめ:現行のSpring Bootで認証を実装する要点
- 現行の設定は
SecurityFilterChainを返すBean+ラムダDSL。WebSecurityConfigurerAdapterは Spring Security 6 で削除され、旧コードはそのままでは動かない。 - 認可の指定は
authorizeHttpRequestsとrequestMatchers(旧authorizeRequests/antMatchersは不可)。パッケージもjavax.*からjakarta.*へ変わった。 - DB上のユーザーで認証するには
UserDetailsServiceの実装とPasswordEncoder(BCrypt)をBean登録する。パスワードのハッシュ化は必須。 - SPA・REST APIではセッションよりトークン(JWT)認証が基本。JJWT 0.12系は
verifyWith/parseSignedClaimsを使う(旧setSigningKey/parseClaimsJwsは非推奨)。 - Spring Boot 4.1 は Spring Security 7.1・Java 17以上で、ラムダDSLが必須。3.5系は2026年6月末でEOL。
以下、認証の全体像から実装コード、旧バージョンからの移行時の注意までを順に見ていく。
Spring Securityによる認証の全体像
Spring Securityは、Spring Bootに依存関係を1つ追加するだけで全リクエストに認証を要求する状態を作れる。その分、どのコンポーネントが何を担当するかを把握しないと、設定をどこに書くべきか迷いやすい。基礎的な位置づけはSpring Securityとは何か?その基本と重要性を解説にまとめているので、ここでは認証を実装する上で押さえるべき登場人物に絞る。
認証(Authentication)と認可(Authorization)の違い
認証はリクエストの送信者が誰かを確かめる処理で、ユーザー名とパスワードやトークンの照合がこれにあたる。認可は、認証済みの利用者がそのリソースにアクセスしてよいかを判定する処理で、Spring Securityでは requestMatchers によるURL単位の制御や、ロール(権限)による制御で表現する。「ログインできるか」が認証、「その画面を開けるか」が認可、と分けて考えると設定の置き場所を間違えにくい。
認証処理の流れ:AuthenticationManagerからUserDetailsServiceまで
フォームログインの場合、送信された資格情報はまず認証用フィルタが Authentication オブジェクトに変換し、AuthenticationManager(標準実装は ProviderManager)へ渡す。ProviderManager は登録された AuthenticationProvider に処理を委譲し、DB認証では DaoAuthenticationProvider が UserDetailsService でユーザーを取得し、PasswordEncoder でパスワードを照合する。成功すると認証済みの Authentication が SecurityContext に格納され、後続の処理から参照できる。フィルタの連鎖そのものの仕組みはSecurityFilterChainとは何かを初心者にもわかりやすく解説で詳しく扱っている。
Spring Bootへの導入と最小構成のセキュリティ設定
依存関係の追加とデフォルトの認証動作
まず spring-boot-starter-security を追加する。MavenとGradleそれぞれの記述は次のとおり。
<!-- Maven: pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
// Gradle: build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
依存関係を足して起動すると、全エンドポイントが認証必須になり、ユーザー名 user の自動生成パスワードが起動ログに Using generated security password: として一度だけ出力される。これは動作確認用で、本番では必ず後述の独自認証に置き換える。
SecurityFilterChainによるアクセス制御(ラムダDSL)
Spring Security 6以降は、設定クラスを継承する方式が廃止され、SecurityFilterChain をBeanとして返す方式に統一された。認可ルールは authorizeHttpRequests の中にラムダで書く。/public 配下だけ公開し、残りを認証必須にする最小構成は次のとおり。
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.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
}
ポイントは3つ。設定クラスに @EnableWebSecurity を付ける必要は(Spring Boot利用時は)なく、@Configuration と SecurityFilterChain Beanだけで足りる。authorizeRequests ではなく authorizeHttpRequests、antMatchers ではなく requestMatchers を使う。そして .and() でつなぐ必要はなく、各設定をラムダで独立して書く。
フォームログインとログアウトの設定
ブラウザ向けのフォームログインとログアウトも同じくラムダで指定する。独自ログイン画面を使う場合は loginPage にパス(例 /login)を渡す。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/login").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/", true)
.permitAll())
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll());
return http.build();
}
loginPage を指定したら、その /login を返すコントローラとテンプレートは自分で用意する。指定しなければSpring Securityが標準のログイン画面を自動生成するので、まず動作を見たい段階では formLogin(withDefaults()) だけでよい。
データベース上のユーザーで認証する
実運用では、固定ユーザーではなくデータベースに保存したユーザーで認証する。必要なのは、ユーザーのエンティティとリポジトリ、そして UserDetailsService の実装、パスワード照合用の PasswordEncoder の4点だ。
ユーザーエンティティとリポジトリ
ユーザー名・ハッシュ化済みパスワード・ロールを持つエンティティを定義する。Spring Boot 3以降は永続化アノテーションが jakarta.persistence パッケージに移っている点に注意する(javax.persistence ではない)。
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class UserAccount {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password; // BCryptでハッシュ化した値を保存する
private String role; // 例: ROLE_USER, ROLE_ADMIN
// getter / setter は省略
}
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserAccountRepository extends JpaRepository<UserAccount, Long> {
Optional<UserAccount> findByUsername(String username);
}
UserDetailsServiceの実装とパスワードのハッシュ化
UserDetailsService は、ユーザー名からユーザー情報を取り出して UserDetails に詰め替えるだけの単純な部品だ。取得できなければ UsernameNotFoundException を投げる。User.withUsername() を使えば独自の UserDetails クラスを自作せずに済む。
import org.springframework.security.core.userdetails.User;
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;
@Service
public class DbUserDetailsService implements UserDetailsService {
private final UserAccountRepository repository;
public DbUserDetailsService(UserAccountRepository repository) {
this.repository = repository;
}
@Override
public UserDetails loadUserByUsername(String username) {
UserAccount user = repository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("user not found: " + username));
return User.withUsername(user.getUsername())
.password(user.getPassword()) // 保存済みのハッシュ値
.authorities(user.getRole())
.build();
}
}
パスワードは必ずハッシュ化して保存し、照合器として PasswordEncoder をBean登録する。標準はBCryptで、より強度を上げたい場合は Argon2PasswordEncoder も選べる。UserDetailsService と PasswordEncoder をBeanとして置いておけば、Spring Bootが DaoAuthenticationProvider を自動で組み立てる。
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// ユーザー登録時は encoder.encode(rawPassword) の戻り値を保存する
AuthenticationManagerを明示的に公開する場合
REST APIのログインエンドポイントを自作するなど、AuthenticationManager を自分で呼び出したい場面がある。Spring Security 6以降は AuthenticationConfiguration から取り出してBean公開するのが定石だ。
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
プロバイダを明示的に組みたいときは、DaoAuthenticationProvider に UserDetailsService と PasswordEncoder を設定し、ProviderManager でくるんで返す方法もある。ただし前述のBean登録だけで多くのケースは足りるため、まずは自動構成を使い、独自の AuthenticationProvider が必要になったら差し替える判断でよい。
SPA・REST APIでのステートレス認証(JWT)
ReactやVueで作るSPAからSpring BootのAPIを叩く構成では、サーバー側でセッションを持たず、トークン(JWT)で認証状態を運ぶのが一般的だ。フロントエンドとの連携全体(CORSやトークンの受け渡し)は【2026年版】Spring BootとReactを連携する方法|REST API・CORS・JWT認証まで実装解説で扱っているので、ここではSpring Security側の実装に絞る。
セッション認証とトークン認証の使い分け
まず前提を整理する。両者は排他ではなく、配信構成で選ぶものだ。
| 観点 | セッション認証 | トークン認証(JWT) |
|---|---|---|
| 状態 | サーバーが保持(ステートフル) | サーバーは保持しない(ステートレス) |
| 保存先 | Cookie(セッションID) | クライアント(Authorizationヘッダ等) |
| CSRF対策 | 必要 | ヘッダ送信なら基本不要 |
| 水平スケール | 共有ストア(Redis等)が要る | 不要(鍵の共有のみ) |
| 向く構成 | 同一ドメイン配信のSPA・従来型Web | 別ドメインAPI・モバイル併用 |
フロントとバックを同じドメインで配信するなら、セッション+Cookieでも安全に組める。JWTが有利になるのは、APIを別ドメインで公開したり、モバイルアプリと共通化したりする場合だ。「流行っているから」でJWTを選ぶと、失効管理やトークン漏洩対策の手間が増える点は割り切って判断したい。
JWTの発行と検証(JJWT 0.12系)
広く使われる io.jsonwebtoken(JJWT)は0.12でAPIが刷新された。古い setSigningKey(String) や parseClaimsJws は非推奨で、現行は署名鍵に SecretKey を渡し、verifyWith/parseSignedClaims を使う。
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
// 256bit以上の鍵を安全に管理する(ハードコードしない)
SecretKey key = Keys.hmacShaKeyFor(secretBytes);
// 発行
String jwt = Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 3600_000)) // 1時間
.signWith(key)
.compact();
// 検証・読み取り
String subject = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jwt)
.getPayload()
.getSubject();
この検証処理を認証用フィルタ(例: OncePerRequestFilter の実装)に置き、Authorization ヘッダの Bearer トークンを取り出して SecurityContext に認証情報を設定する、という流れになる。
CSRF・CORSの設定とSPAでの注意点
ステートレスなJWT認証では、セッションを作らせない設定にしたうえでCSRFを無効化する。トークンをヘッダで送る限りCSRFの前提(Cookie自動送信)が成立しないためだ。
import org.springframework.security.config.http.SessionCreationPolicy;
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.cors(cors -> {}); // CorsConfigurationSource Beanで許可オリジンを定義
ただし csrf().disable() は「ステートレスAPIだから」という理由がある場合の判断だ。セッション+Cookieで認証するSPAで安易にCSRFを無効化するのは危険で、その場合は無効化ではなく CookieCsrfTokenRepository.withHttpOnlyFalse() でトークンをJSから読める形にして送り返す構成を採る。CORSは CorsConfigurationSource Beanで許可オリジン・メソッド・ヘッダを明示し、ワイルドカードの多用は避ける。
認証情報の取得と、避けるべき設定
SecurityContextHolderからログインユーザーを取得
認証済みユーザーの情報は SecurityContextHolder から取得できる。コントローラでは @AuthenticationPrincipal で直接受け取るほうが簡潔だ。
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
@GetMapping("/me")
public String me(@AuthenticationPrincipal UserDetails user) {
return user.getUsername();
}
// フィルタやサービス層からは SecurityContextHolder を直接参照する
// Authentication auth = SecurityContextHolder.getContext().getAuthentication();
やってはいけない設定・失敗パターン
認証まわりの事故は、動くコードをそのまま本番へ持ち込んだ結果として起きやすい。次の設定は避ける。
パスワードの平文保存。 NoOpPasswordEncoder は検証・学習用途に限る。本番でこれを使うと、DB流出時に全ユーザーのパスワードがそのまま漏れる。必ずBCryptなどでハッシュ化する。
署名鍵のハードコードと短い鍵。 JWTの署名鍵をソースに直書きすると、リポジトリ経由で漏洩する。HS256は256bit(32バイト)以上の鍵が必要で、短い文字列はJJWTが例外を投げる。鍵は環境変数やシークレットマネージャで管理する。
ブラウザ認証でのCSRF無効化。 セッション+Cookieで認証しているのにCSRFを切ると、正規利用者になりすました強制リクエストを防げない。無効化はステートレスAPIに限定する。
permitAll() の付けすぎ。 動作確認のために広く許可したまま公開すると、保護すべきエンドポイントが素通しになる。anyRequest().authenticated() を基本線にし、公開範囲は最小にする。
Spring Security 5→6→7で変わった点(古いコードが動かない理由)
ネット上のSpring Security解説は5系(Spring Boot 2系)を前提にしたものが今も多く、そのままでは現行環境でコンパイルが通らない。つまずきやすい変更点を整理しておく。
WebSecurityConfigurerAdapterの廃止とSecurityFilterChain
Spring Security 6.0で WebSecurityConfigurerAdapter は削除された。継承して configure(HttpSecurity) をオーバーライドする書き方はもう使えず、SecurityFilterChain をBeanとして返す構成に置き換える。設定を「継承」から「Beanの組み立て」へ発想を変えるのがこの移行の核心だ。
認可DSLも改名された。authorizeRequests() は authorizeHttpRequests()、URLパターン指定の antMatchers()/mvcMatchers() は requestMatchers() に統一された。あわせて .and() による連結は不要になり、各設定をラムダで独立して書く「ラムダDSL」が標準になった。ラムダDSLはSpring Security 7.0では必須で、旧スタイルは使えなくなる。
javax→jakarta とバージョン対応表
Spring Boot 3でJakarta EE 9+へ移行したため、javax.servlet.* や javax.persistence.* は jakarta.* に変わった。フィルタやエンティティのimport文を機械的に置換する必要がある。主要バージョンの対応は次のとおり。
| Spring Boot | Spring Framework | Spring Security | Java(最小) | 状態(2026-07) |
|---|---|---|---|---|
| 4.1 | 7.0 | 7.1 | 17 | 最新(2026-06) |
| 4.0 | 7.0 | 7.0 | 17 | サポート中 |
| 3.5 | 6.2 | 6.5 | 17 | 2026-06-30 EOL |
| 3.4 | 6.2 | 6.4 | 17 | EOL済 |
| 2.7 | 5.3 | 5.8 | 8 | OSS EOL済 |
これから新規に書くなら、ラムダDSLを前提にSpring Boot 3.5以上、可能なら4.1で組むのが無難だ。バージョンごとのサポート期限はSpring Bootバージョン一覧とサポート期限【2026年7月】最新4.1と3.5 EOL後の選び方、4系の変更点はSpring Boot 4とは?最新バージョン4.1の変更点・新機能とSpring Boot 3との違いを解説【2026年最新】を参照してほしい。
よくある質問
Spring Bootのデフォルトのログインパスワードはどこで確認できますか?
アプリ起動時のコンソールログに Using generated security password: の行が一度だけ出力され、そこに表示されます。ユーザー名は user です。この自動生成パスワードは動作確認用なので、本番では UserDetailsService による独自認証に置き換えます。
WebSecurityConfigurerAdapterが使えないのはなぜですか?
Spring Security 6.0で削除されたためです。SecurityFilterChain をBeanとして返す構成へ書き換えてください。継承ベースの旧コードは、Spring Boot 3以降ではコンパイルできません。
認証と認可はどう違いますか?
認証は「誰か」を確かめる処理(ログインの成否)、認可は「何を許すか」を判定する処理(画面やAPIへのアクセス可否)です。Spring Securityでは認証を UserDetailsService 側、認可を requestMatchers 側で表現します。
SPA(ReactやVue)ではセッションとJWTのどちらを使うべきですか?
フロントとAPIを同一ドメインで配信するなら、セッション+Cookie(CSRFトークン付き)で十分かつ安全です。APIを別ドメインで公開する、モバイルアプリと認証を共通化する、といった要件があるときにJWTを選びます。
パスワードは平文で保存してよいですか?
いけません。PasswordEncoder(BCryptやArgon2)でハッシュ化して保存します。NoOpPasswordEncoder は平文をそのまま扱うため、学習・検証用途に限り、本番では使わないでください。