spring-boot-security-jwt について
Spring Boot 開発者向けの JWT セキュリティ実装スキルです。ステートレス認証、トークンローテーション、細かな認可のパターンを扱い、Spring Security 6.x と JJWT を対象とします。
主な機能
- 01アクセストークンとリフレッシュトークンの生成・検証
- 02Bearer と HttpOnly Cookie による認証
- 03リフレッシュトークンのローテーション、失効、ブラックリスト化
- 04ロール・権限ベースの認可
- 05OAuth2 連携とセキュリティテスト
活用シーン
- 01REST API への JWT 認証追加
- 02ログイン、更新、ログアウト処理の実装
- 03ロールまたは権限によるアクセス制御の設定
- 04Spring Security 設定のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill spring-boot-security-jwtインストール後、Claude Code で/spring-boot-security-jwtを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/generate-jwt-keys.shreferences/authorization-patterns.mdconfiguration.mdexamples.mdjwt-complete-configuration.mdjwt-configuration.mdjwt-quick-reference.mdjwt-testing-guide.mdmicroservices-security.mdmigration-spring-security-6x.mdoauth2-integration.mdperformance-optimization.mdsecurity-hardening.mdstructure.mdtesting-jwt-security.mdtesting.mdtoken-management.mdtroubleshooting.mdscripts/test-jwt-setup.sh詳細
Spring Boot JWT Security
Spring Security 6.x と JJWT を使用した Spring Boot 3.5.x 向けの JWT 認証・認可パターンです。トークンの生成、検証、更新戦略、RBAC/ABAC、OAuth2 連携について説明します。
概要
このスキルでは、Spring Boot アプリケーションでステートレスな JWT 認証を実装するためのパターンを紹介します。JJWT 0.12.6 によるトークン生成、Bearer/Cookie ベースの認証、リフレッシュトークンのローテーション、@PreAuthorize 式を使ったメソッドレベルの認可など、認証フロー全体を扱います。
主な機能:
- 有効期限を設定できるアクセストークンとリフレッシュトークンの生成
- Bearer トークンおよび HttpOnly Cookie による認証方式
- Spring Data JPA および OAuth2 プロバイダーとの連携
- ロール/権限ベースの
@PreAuthorizeルールによる RBAC - ログアウト/ローテーション時のトークン失効とブラックリスト登録
使用する場面
ユーザーから次のような依頼があった場合に有効化します:
- 「JWT 認証を実装して」「トークンで REST API を保護して」
- 「Spring Security 6.x の設定」「SecurityFilterChain の設定」
- 「ロールベースのアクセス制御」「RBAC」
`@PreAuthorize` - 「リフレッシュトークン」「トークンのローテーション」「トークンの失効」
- 「OAuth2 連携」「ソーシャルログイン」「Google/GitHub 認証」
- 「ステートレス認証」「SPA バックエンドのセキュリティ」
- 「JWT フィルター」「OncePerRequestFilter」「Bearer トークン」
- 「Cookie ベースの JWT」「HttpOnly Cookie」
- 「権限ベースのアクセス制御」「カスタム PermissionEvaluator」
クイックリファレンス
依存関係(JJWT 0.12.6)
| 成果物 | スコープ |
|---|---|
spring-boot-starter-security | コンパイル |
spring-boot-starter-oauth2-resource-server | コンパイル |
io.jsonwebtoken:jjwt-api:0.12.6 | コンパイル |
io.jsonwebtoken:jjwt-impl:0.12.6 | 実行時 |
io.jsonwebtoken:jjwt-jackson:0.12.6 | 実行時 |
spring-security-test | テスト |
Maven と Gradle の記述例については、references/jwt-quick-reference.md を参照してください。
主な設定プロパティ
| プロパティ | 設定例 | 備考 |
|---|---|---|
jwt.secret | ${JWT_SECRET} | 最小 256 ビット。ハードコードしないでください |
jwt.access-token-expiration | 900000 | ミリ秒単位で 15 分 |
jwt.refresh-token-expiration | 604800000 | ミリ秒単位で 7 日 |
jwt.issuer | my-app | すべてのトークンで検証されます |
jwt.cookie-name | jwt-token | Cookie ベースの認証用 |
jwt.cookie-http-only | true | 本番環境では常に true |
jwt.cookie-secure | true | HTTPS 使用時は常に true |
認可アノテーション
| アノテーション | 例 |
|---|---|
@PreAuthorize("hasRole('ADMIN')") | ロールの確認 |
@PreAuthorize("hasAuthority('USER_READ')") | 権限の確認 |
@PreAuthorize("hasPermission(#id, 'Doc', 'READ')") | ドメインオブジェクトの確認 |
@PreAuthorize("@myService.canAccess(#id)") | Spring Bean の確認 |
手順
ステップ 1 — 依存関係を追加する
ビルドファイルに spring-boot-starter-security、spring-boot-starter-oauth2-resource-server、および JJWT の 3 つの成果物を含めます。Maven/Gradle の正確な記述例については、references/jwt-quick-reference.md を参照してください。
ステップ 2 — application.yml を設定する
jwt:
secret: ${JWT_SECRET:change-me-min-32-chars-in-production}
access-token-expiration: 900000
refresh-token-expiration: 604800000
issuer: my-app
cookie-name: jwt-token
cookie-http-only: true
cookie-secure: false # true in production
プロパティの全リファレンスについては、references/jwt-complete-configuration.md を参照してください。
ステップ 3 — JwtService を実装する
主な処理:アクセストークンの生成、リフレッシュトークンの生成、ユーザー名の抽出、トークンの検証。
@Service
public class JwtService {
public String generateAccessToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.issuer(issuer)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + accessTokenExpiration))
.claim("authorities", getAuthorities(userDetails))
.signWith(getSigningKey())
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
} catch (JwtException e) {
return false;
}
}
}
鍵管理とクレーム抽出を含む JwtService の完全な実装については、references/jwt-complete-configuration.md を参照してください。
ステップ 4 — JwtAuthenticationFilter を作成する
OncePerRequestFilter を拡張して、Authorization: Bearer ヘッダー(または HttpOnly Cookie)から JWT を抽出し、検証してから SecurityContext に設定します。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}
Cookie ベースの実装については、references/configuration.md を参照してください。
ステップ 5 — SecurityFilterChain を設定する
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**", "/swagger-ui/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
CORS、ログアウトハンドラー、OAuth2 ログインの統合については、references/jwt-complete-configuration.md を参照してください。
ステップ 6 — 認証エンドポイントを作成する
@RestController を介して /register、/authenticate、/refresh、/logout を公開します。レスポンスボディで accessToken と refreshToken を返します(必要に応じて HttpOnly Cookie も設定します)。
AuthenticationController と AuthenticationService の完全な実装については、references/examples.md を参照してください。
ステップ 7 — リフレッシュトークン戦略を実装する
user_id、expiry_date、revoked、expired の各列を含むリフレッシュトークンをデータベースに保存します。/refresh では、保存されているトークンを検証して無効化し、新しいトークンのペアを発行します(トークンローテーション)。
RefreshToken エンティティ、ローテーションのロジック、Redis ベースのブラックリスト化については、references/token-management.md を参照してください。
ステップ 8 — 認可ルールを追加する
細かな制御には、@EnableMethodSecurity と @PreAuthorize アノテーションを使用します。
@PreAuthorize("hasRole('ADMIN')")
public Page<UserResponse> getAllUsers(Pageable pageable) { ... }
@PreAuthorize("hasPermission(#documentId, 'Document', 'READ')")
public Document getDocument(Long documentId) { ... }
RBAC エンティティモデル、PermissionEvaluator、ABAC パターンについては、references/authorization-patterns.md を参照してください。
ステップ 9 — セキュリティテストを作成する
@SpringBootTest
@AutoConfigureMockMvc
class AuthControllerTest {
@Test
void shouldDenyAccessWithoutToken() throws Exception {
mockMvc.perform(get("/api/orders"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminAccess() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
}
テストスイート一式、Testcontainers のセットアップ、セキュリティテストのチェックリストについては、references/testing.md と references/jwt-testing-guide.md を参照してください。
ベストプラクティス
トークンのセキュリティ
- 最低 256 ビットの秘密鍵を使用します。環境変数から読み込み、ハードコードしないでください
- アクセストークンの有効期間は短く設定します(15 分)。長時間のセッションにはリフレッシュトークンを使用します
- トークンローテーションを実装します。新しいリフレッシュトークンを発行する際は、古いリフレッシュトークンを無効化します
- ログアウト時のブラックリスト化には
jti(JWT ID)クレームを使用します
Cookie と Bearer ヘッダー
- ブラウザクライアントには HttpOnly Cookie を優先します(XSS 対策)
- モバイル/API クライアントには
Authorization: Bearerヘッダーを使用します - 本番環境では Cookie に
SecureとSameSite=LaxまたはStrictを設定します
Spring Security 6.x
SecurityFilterChainBean を使用し、WebSecurityConfigurerAdapterは決して継承しないでください- CSRF を無効にするのはステートレス API の場合に限ります。セッションベースのフローでは有効にしておきます
- 非推奨の
@EnableGlobalMethodSecurityではなく、@EnableMethodSecurityを使用します issとaudのクレームを検証し、信頼できない発行者のトークンを拒否します
パフォーマンス
- リクエストごとの DB 検索を避けるため、
@CacheableでUserDetailsをキャッシュします - 署名鍵の導出をキャッシュします(リクエストごとに HMAC 鍵を再計算しないでください)
- 大規模運用では、リフレッシュトークンの保存に Redis を使用します
してはいけないこと
- JWT クレームに機密データ(パスワード、PII)を保存しないでください。クレームは署名されるだけで、暗号化はされません
- 有効期間が無期限のトークンを発行しないでください
- 署名と有効期限を検証せずにトークンを受け入れないでください
- 環境間で署名鍵を共有しないでください
例
基本的な認証フロー
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/authenticate")
public ResponseEntity<AuthResponse> authenticate(
@RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.authenticate(request));
}
@PostMapping("/refresh")
public ResponseEntity<AuthResponse> refresh(@RequestBody RefreshRequest request) {
return ResponseEntity.ok(authService.refreshToken(request.refreshToken()));
}
@PostMapping("/logout")
public ResponseEntity<Void> logout() {
authService.logout();
return ResponseEntity.ok().build();
}
}
コントローラーメソッドでの JWT 認可
@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {
@GetMapping("/users")
public ResponseEntity<List<UserResponse>> getAllUsers() {
return ResponseEntity.ok(adminService.getAllUsers());
}
}
エンティティモデルとサービス実装の完全な例については、references/examples.md を参照してください。
参考資料
| ファイル | 内容 |
|---|---|
| references/jwt-quick-reference.md | 依存関係、最小構成のサービス、一般的なパターン |
| references/jwt-complete-configuration.md | 完全な設定:プロパティ、SecurityFilterChain、JwtService、OAuth2 RS |
| references/configuration.md | JWT 設定 Bean、CORS、CSRF、エラー処理、セッションオプション |
| references/examples.md | アプリケーションの完全なセットアップ:コントローラー、サービス、エンティティ |
| references/authorization-patterns.md | RBAC/ABAC エンティティモデル、PermissionEvaluator、SpEL 式 |
| references/token-management.md | リフレッシュトークンのエンティティ、ローテーション、Redis によるブラックリスト化 |
| references/testing.md | ユニットテストと MockMvc テスト、テストユーティリティ |
| references/jwt-testing-guide.md | Testcontainers、負荷テスト、セキュリティテストのチェックリスト |
| references/security-hardening.md | セキュリティヘッダー、HSTS、レート制限、監査ログ |
| references/performance-optimization.md | Caffeine キャッシュ設定、非同期検証、コネクションプーリング |
| references/oauth2-integration.md | Google/GitHub OAuth2 ログイン、OAuth2UserService |
| references/microservices-security.md | サービス間の JWT 伝播、リソースサーバー設定 |
| references/migration-spring-security-6x.md | Spring Security 5.x からの移行 |
| references/troubleshooting.md | 一般的なエラー、デバッグのヒント |
制約と警告
セキュリティ上の制約
- JWT トークンは署名されていますが、暗号化されていません。クレームに機密データを含めないでください
- トークンを信頼する前に、必ず
exp、iss、audクレームを検証してください - 署名キーは 256 ビット以上である必要があります。本番環境では決して脆弱なキーを使用しないでください
- シークレットは環境変数またはセキュアな Vault から読み込み、設定ファイルから読み込まないでください
- Cookie ベースのフローで CSRF を防ぐには、SameSite Cookie 属性が不可欠です
Spring Security 6.x の制約
WebSecurityConfigurerAdapterは削除されました。SecurityFilterChainBean のみを使用してください@EnableGlobalMethodSecurityは非推奨です。@EnableMethodSecurityを使用してくださいHttpSecurityの設定には Lambda DSL が必須です(メソッドチェーンは使用できません)WebSecurityConfigurerAdapter.order()は、@Configurationクラスに付与する@Orderに置き換えられました
トークンの制約
- セキュリティのため、アクセストークンの有効期限は 5~15 分に設定してください
- リフレッシュトークンはサーバー側(DB または Redis)に保存し、localStorage には保存しないでください
- ログアウト時に即時失効できるよう、トークンのブラックリスト化を実装してください
- トークンのブラックリスト化を正しく機能させるには、
jtiクレームが必要です
関連スキル
spring-boot-dependency-injection— 全体で使用するコンストラクターインジェクションのパターンspring-boot-rest-api-standards— REST API のセキュリティパターンとエラー処理unit-test-security-authorization— Spring Security 設定のテストspring-data-jpa— ユーザーエンティティとリポジトリーのパターンspring-boot-actuator— セキュリティ監視とヘルスエンドポイント