JWTの認証フィルターを作成する方法|Spring Securityでの実装手順を初心者向けに解説!
新人
「Spring SecurityでJWTを使った認証をやりたいんですけど、認証フィルターって何をするんですか?」
先輩
「認証フィルターは、ログイン済みかどうかをチェックするゲートみたいな役割だよ。JWTを使う場合は、このフィルターでトークンを検証してから処理を進めるんだ。」
新人
「なるほど、トークンの中身を確認してユーザーが誰かを判断してるんですね。実装も難しそう…」
先輩
「確かに初めてだと戸惑うかも。でも一つずつ理解していけば、Spring SecurityのJWT認証フィルターの仕組みはそんなに難しくないよ!」
1. JWT認証フィルターとは何か?
Spring SecurityでのJWT認証フィルターとは、リクエストに含まれるJWTトークンを検査し、正しいユーザー情報かどうかを確認する役割を担う重要なコンポーネントです。セッション方式とは異なり、トークンベースの認証では各リクエストごとに検証が行われるため、ステートレスなAPI設計に向いています。
JWTフィルターの主な処理は次の通りです:
- リクエストのAuthorizationヘッダーからJWTトークンを取得
- トークンが正しく署名されているか、有効期限内かを検証
- ユーザー情報を取得し、Spring Securityの認証オブジェクトとしてセット
これにより、各リクエストごとに「そのユーザーが本当にログイン済みかどうか」を安全にチェックできます。
特に「Spring Security JWT 認証フィルター」というキーワードで調べる人が多く、実装例や手順をわかりやすく解説することが初心者にとって非常に重要です。
2. 認証フィルターの基本的な役割とJWTの関係
Spring Securityでは、すべてのリクエストが一連の「フィルター」を通過します。その中に自作のJWT認証フィルターを追加することで、トークンによる認証処理を独自にカスタマイズできます。JWTフィルターは、そのリクエストが保護されたリソースにアクセスする前に、認証されているかどうかを確認するチェックポイントです。
JWTを使った認証フィルターの中でよく使われるのが、Spring SecurityのOncePerRequestFilterクラスを継承する方法です。名前の通り、リクエストごとに一度だけ実行されるフィルターで、ここでJWTの検証やユーザー情報の設定が行えます。
以下は、JWTフィルター作り方としてよく紹介される基本構成のサンプルです:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
String username = jwtUtil.extractUsername(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
このコードでは、JWTをヘッダーから取り出し、有効であればSpring Securityにユーザーとして認識させています。認証済みユーザーとして扱われることで、コントローラーなどで@PreAuthorizeやhasRole()などの認可機能が使えるようになります。
また、このフィルターをどのようにSecurityFilterChainに登録するかが、次のステップで重要になりますが、それは中盤の記事で詳しく紹介します。
初心者の方は、JWTと認証フィルターの関係をしっかり押さえておくことで、Spring SecurityでのJWT認証の流れを理解しやすくなります。
3. フィルタークラスの作成方法(OncePerRequestFilterを継承)
Spring Security Filter JWTを実装する際の第一歩は、OncePerRequestFilterを継承したクラスを作成することです。OncePerRequestFilterは、名前の通り、リクエストごとに一度だけ実行されるフィルターです。
このカスタムフィルターを作ることで、JWTトークンの抽出や検証、Spring Securityコンテキストへの認証情報の登録などを行うことができます。以下に、JWT 認証フィルター 実装の基本形を示します。
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.extractUsername(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
}
上記のように、JWTトークンの抽出処理を専用のメソッドに分けることで、コードの見通しが良くなります。また、トークンの検証処理は後述するjwtUtil.validateTokenメソッドに委ねています。
4. JWTトークンの検証処理の実装(ユーザー情報の抽出、エラー処理など)
次に、JWTトークンの検証とユーザー情報の抽出を行う処理を実装します。この処理は通常、JwtUtilのようなユーティリティクラスにまとめておきます。
JWTのトークンは署名付きで生成されており、トークンを受け取った側ではこの署名の正当性や有効期限などを検証する必要があります。
以下はJwtUtilクラスの例です。
@Component
public class JwtUtil {
private final String secretKey = "my-secret-key";
public String extractUsername(String token) {
return getClaims(token).getSubject();
}
public boolean validateToken(String token) {
try {
Claims claims = getClaims(token);
return !claims.getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
}
}
このクラスでは、トークンの署名と有効期限を検証し、問題がなければユーザー名(サブジェクト)を取得します。トークンが改ざんされていたり、有効期限を過ぎていた場合は例外が発生し、falseを返す仕組みです。
このようにJwtUtilを使えば、JWT 認証フィルター 実装の際に複雑な処理を簡潔にまとめられます。
5. SecurityFilterChainへの組み込み方法(Bean登録)
作成したJwtAuthenticationFilterをSpring Securityの認証フローに組み込むには、SecurityFilterChainの設定が必要です。SecurityFilterChainは、Spring Security 5.7以降で推奨されているセキュリティ設定の方法です。
以下は、フィルターを登録する方法の一例です。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtUtil jwtUtil;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
.addFilterBefore()メソッドを使って、UsernamePasswordAuthenticationFilterの前にJwtAuthenticationFilterを追加することで、トークンの検証が認証処理の前に行われるようになります。
これにより、リクエストがSpring Securityに到達する前にJWTトークンがチェックされ、認証済みかどうかの判定が可能になります。
以上のステップで、Spring Security Filter JWTの設定は完了です。自作したJWT認証フィルターがセキュリティ機能の一部として動作するようになり、ログイン不要のAPIと認証必須のAPIを安全に分けて運用できます。
6. 認証フィルターが動作する流れの確認(リクエスト〜認証処理まで)
Spring Security フィルター 順序を理解することは、JWT認証が正しく動作するために非常に重要です。では実際に、リクエストから認証処理が完了するまでの流れを確認してみましょう。
1つのリクエストがWebアプリケーションに届いたとき、最初に通過するのがSpring Securityのフィルターチェーンです。その中で自作したJwtAuthenticationFilterは、UsernamePasswordAuthenticationFilterの前に挿入されているため、パスワード認証処理よりも前にJWTトークンの検証が行われます。
フィルターの内部では、以下のような流れで処理が進みます:
- ① リクエストヘッダーに含まれる
AuthorizationからBearerトークンを取得 - ②
JwtUtilを使ってトークンの有効性を確認(署名や有効期限など) - ③ トークンに含まれるユーザー情報を取り出し、
Authenticationオブジェクトを生成 - ④ セキュリティコンテキストに認証情報をセット
このようにして認証が完了すると、後続の処理やコントローラーではそのユーザーがログイン済みとして扱われるようになります。
つまり、JWT 認証フィルター 実装の効果が、リクエストごとに確実に働くというわけです。
7. よくあるエラーとその対策(NullPointerException、トークン無効など)
JWT認証フィルターを導入すると、実装初期にいくつかのエラーに遭遇することがあります。ここではNullPointerExceptionやトークンの不正など、初心者がつまずきやすい問題とその解決策を紹介します。
① NullPointerExceptionが発生する
よくある原因は、JWTトークンがリクエストに含まれていないにもかかわらず、そのトークンから情報を取得しようとしているケースです。以下のように、nullチェックを入れておくことで防げます。
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
// 認証処理を実行
}
また、SecurityContextHolderへのアクセス時もnullチェックを忘れないようにしましょう。安全に扱うことがセキュリティ上も大切です。
② JWTトークンが無効と判定される
「JWT 認証 エラー 対処法」として代表的なのが、有効期限切れや署名不一致などによるトークンエラーです。
その場合は、JwtUtilの中で例外を明確にキャッチし、ログに記録するなどの処理を追加しましょう。
public boolean validateToken(String token) {
try {
Claims claims = getClaims(token);
return !claims.getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
System.out.println("トークンの有効期限切れ");
} catch (SignatureException e) {
System.out.println("署名が無効です");
} catch (Exception e) {
System.out.println("トークンの検証に失敗しました");
}
return false;
}
このように詳細なエラーメッセージを出すことで、問題の切り分けがしやすくなります。
③ フィルターが通っていない
フィルターが機能していない原因の多くは、Spring Security フィルター 順序の設定ミスです。.addFilterBefore()の位置が適切かどうかを見直しましょう。
UsernamePasswordAuthenticationFilterの「前」に自作のJWT認証フィルターを追加するのが正解です。
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
ここを間違えると、認証がスキップされるため注意しましょう。
8. 初心者におすすめの学習手順(JWT認証処理の習得ステップ)
最後に、JWTを使った認証処理を段階的に学ぶためのステップを紹介します。最初から全部を理解しようとせず、1つずつ丁寧に進めることが大切です。
ステップ①:Spring Securityの基本を理解
まずはSpring Securityの仕組みを把握しましょう。セキュリティチェーンやAuthentication、SecurityContextの役割を学ぶことで、JWT認証の全体像が見えてきます。
ステップ②:JWTの構造と生成方法を学ぶ
JWTは3つのパート(ヘッダー・ペイロード・署名)から構成されており、それぞれの意味やトークンの生成方法を理解することが重要です。自作のJwtUtilクラスを使ってトークンを生成・検証する練習をしましょう。
ステップ③:フィルターのカスタマイズに挑戦
Spring Security Filter JWTのカスタマイズでは、OncePerRequestFilterの継承や、SecurityFilterChainへの組み込みがポイントです。動作確認やログ出力を交えながら、処理の流れを追ってみてください。
ステップ④:認可機能との連携を試す
最後に、取得した認証情報を使って、@PreAuthorizeやhasRole()などの認可アノテーションと連携させてみましょう。セキュリティの実用的な部分が見えてきます。
このようにステップごとに取り組むことで、初心者でも確実にJWT 認証フィルター 実装を習得できます。焦らずじっくり進めていきましょう。