spring-security-jwt について
Spring 開発者向けの JWT セキュリティスキルです。ステートレスな Bearer 認証、アカウント状態の確認、トークン処理のテンプレートと指針を提供し、よくある設定ミスを防ぎます。
主な機能
- 01JWT サービスと認証フィルターのテンプレート
- 02ステートレスな Bearer 認証設定
- 03ロールおよびメソッド単位の認可
- 04JSON による 401・403 エラー処理
- 05アクセストークンとリフレッシュトークンの指針
活用シーン
- 01Spring API への JWT Bearer 認証の追加
- 02トークン認証時のアカウント状態確認
- 03エンドポイントとメソッドへのロールベースアクセス制御
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill spring-security-jwtインストール後、Claude Code で/spring-security-jwtを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/bad-security-config.javagood-security-config.javatemplates/JwtAuthenticationFilter.javaJwtService.javaSecurityConfig.java詳細
Spring Security — JWT
依存関係
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
セキュリティ設定
コンパイル済みのSecurityConfigを、以下のフィルターおよびサービスとともに使用します。 この設定では、ラムダ DSL、ステートレスなベアラー認証、ロールルール、JSON 形式の 401/403 ハンドラーを使用し、セキュリティチェーンのフィルターがサーブレットに登録されないようにしています。Bean メソッドインジェクションを使うことで、設定クラスと、その設定クラス自身の AuthenticationProvider Bean の間で発生するコンストラクターの循環依存を回避します。プロジェクトに合わせてルートとロールを調整してください。CSRF の無効化は、ヘッダーのみのベアラー API に適用されます。ブラウザーが認証 Cookie を自動的に送信する場合は、CSRF 保護を維持してください。
JWT の実装
テスト済みのJwtServiceと
JwtAuthenticationFilterのテンプレートを組み合わせて使用します。
設定キーはapp.jwt.secret、app.jwt.access-token-expiration、および
app.jwt.refresh-token-expirationです(期間の単位はミリ秒です)。
このフィルターは、有効期限切れ、形式不正、改ざん済み、有効期限の欠落したトークン、およびリフレッシュトークンを拒否し、401 と Bearer チャレンジを返します。認証の前に、現在のユーザーの有効、ロック、アカウント期限切れ、および認証情報期限切れの各フラグを確認します。削除済みユーザーにも 401 を返します。データベースの停止や下流のアプリケーション障害は、無効な認証情報として処理せず、サーバーエラーのままにする必要があります。無効なトークンが提示された場合は、公開エンドポイントでも拒否します。
フィルターのエラー応答例では Problem Details を使用します。既存のレガシー API では、確立済みのエラー契約に合わせて、この応答と以下のエントリーポイントを調整してください。ベアラートークンをログに記録しないでください。フィルター Bean はセキュリティチェーン内でのみ登録します。enabledフラグを false に設定したFilterRegistrationBean<JwtAuthenticationFilter>を使って、サーブレットコンテナーへの登録を無効にしてください。
これらのテンプレートは、単一サービス内で利用するファーストパーティのトークン契約を例示しています。サービス間で署名キーを共有したり、サービス間でトークンを受け入れたりする前に、発行者とオーディエンス、キーのローテーション、および失効を定義して検証してください。Spring Security のリソースサーバーサポートでもカスタム JWT を検証できます。アプリケーションにすでに適合している場合は、そのサポートを維持してください。トークンの生成だけでは、完全なリフレッシュフローにはなりません。以下のローテーションと再利用検出の要件を維持してください。
JSON 形式の 401/403
設定とフィルターのテンプレートでは、認証および認可エラーに Problem Details を使用します。レガシーのエラー契約を使う場合は、両方を合わせて調整してください。保護されたルートで認証情報がない場合は 401 を返します。認証済みの呼び出し元に必要なロールがない場合は 403 を返します。無効なトークンが提示された場合は、そのルートで匿名アクセスが許可されていても 401 を返します。 コントローラーアドバイスでは、ディスパッチャーサーブレットより前にスローされた例外を処理できません。
認証コントローラー
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ApiResponse<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
return ApiResponse.ok(authService.login(request));
}
@PostMapping("/refresh")
public ApiResponse<AuthResponse> refresh(@Valid @RequestBody RefreshRequest request) {
return ApiResponse.ok(authService.refresh(request.refreshToken()));
}
@PostMapping("/register")
public ResponseEntity<ApiResponse<AuthResponse>> register(@Valid @RequestBody RegisterRequest request) {
return ResponseEntity.status(201).body(ApiResponse.ok(authService.register(request)));
}
}
public record AuthResponse(String accessToken, String refreshToken, long expiresIn) {}
メソッドレベルのセキュリティ
// On service methods
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(UUID userId) { ... }
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public UserProfile getProfile(UUID userId) { ... }
@PostAuthorize("returnObject.email == authentication.name")
public User findById(UUID id) { ... }
application.yml
app:
jwt:
secret: ${JWT_SECRET} # min 256-bit base64 encoded key
access-token-expiration: 900000 # 15 minutes
refresh-token-expiration: 604800000 # 7 days
この例ではリフレッシュトークンを作成しますが、リフレッシュエンドポイントは実装していません。本番環境のリフレッシュフローでは、type=refreshのトークンのみを受け入れ、使用のたびにリフレッシュトークンをローテーションし、以前のトークンを失効させる必要があります(たとえば、データベースまたは Redis にトークンファミリーのハッシュ化されたレコードを保存します)。
注意点
- エージェントがユーザー検索時に
AuthenticationExceptionを広く捕捉します。サーバー障害としてAuthenticationServiceExceptionを維持してください。 - エージェントが有効な JWT を使って無効化またはロックされたユーザーをログインさせます。クレームだけでなく、現在のアカウント状態も検証してください。
- エージェントが古い API の
HttpSecurity.csrf().disable()を使います。AbstractHttpConfigurer::disableを使ってください。 - エージェントが
ExpiredJwtExceptionをフィルターから漏らします。期限切れトークンは 401 ではなく 500 になるため、フィルター内で捕捉してください。 - エージェントが
exceptionHandling()を省略します。クライアントには空の 401/403 レスポンス本文(またはログインページへのリダイレクト)が返されます。@RestControllerAdviceではフィルターレベルの例外を捕捉できません。 - エージェントが JWT シークレットをコード内に保存します。常に環境変数の
${JWT_SECRET}を使ってください(HS256 には 256 ビット以上のキーが必要です。そうでない場合、Keys.hmacShaKeyForがWeakKeyExceptionをスローします)。 - エージェントが
SessionCreationPolicy.IF_REQUIREDを使います。JWT ではSTATELESSにする必要があります。 - エージェントが署名と有効期限しか検証しません。ベアラーフィルターでは
type=accessトークンのみを受け付ける必要があります。リフレッシュトークンは専用のリフレッシュエンドポイントで扱ってください。 - エージェントが
@PreAuthorizeの動作に必要な@EnableMethodSecurityを設定し忘れます。 - エージェントが BCrypt の強度を 10 未満に設定します。本番環境では 12 を使ってください。
- エージェントがトークン検証ロジックをコントローラーに配置します。これはフィルターに配置してください。
- エージェントが例でリフレッシュトークンを localStorage に保存します。httpOnly クッキーまたは安全なストレージを推奨してください。リフレッシュトークンは有効期間の長い認証情報です。