JWTトークンの発行(Spring Security + JWT)を初心者向けにやさしく解説!
新人
「Spring Securityで認証をするってよく聞くんですが、最近はJWTも使うって聞きました。そもそもJWTって何なんですか?」
先輩
「JWTはJSON Web Tokenの略で、ログイン後にユーザー情報を含んだトークンを発行して、その後のリクエストに使う仕組みだよ。」
新人
「えっ、セッションとか使わないんですか?セッションIDで管理するのが普通じゃないんですか?」
先輩
「セッションを使う方法もあるけど、JWTは“ステートレス認証”っていって、サーバーに状態を持たせずに認証ができるんだ。今回はその基本を解説していこう!」
1. JWTとは何か?なぜ必要か?
JWT(ジェイダブリューティー)とは「JSON Web Token(ジェイソン・ウェブ・トークン)」の略称で、ログイン後のユーザー情報や認証状態を安全にやり取りするためのトークン技術です。ログイン認証において、セッションを使わずにユーザーを識別できる仕組みとして注目されています。
Spring BootなどのWebアプリでは、ログイン後にJWTトークンを発行し、クライアントに返します。以降のリクエストにはこのトークンをヘッダーに含めて送信することで、サーバーは誰からのリクエストかを判断できます。
JWTを使うと次のようなメリットがあります:
- サーバー側にセッション情報を持たず、ステートレスでスケーラブル
- 署名付きで改ざんが防げるため、安全性が高い
- モバイルアプリやフロントエンドSPAとも相性が良い
JWTは3つの部分で構成されています:
- ヘッダー:トークンの種類や署名アルゴリズムを記載
- ペイロード:ユーザーIDなどの情報(クレーム)を格納
- 署名:改ざんを防ぐためのハッシュ値
具体的には、以下のような形式で表現されます:
xxxxx.yyyyy.zzzzz
このようにして、JWTを使えば1つの文字列で認証状態を表現できるため、セッション管理の手間が省け、RESTfulなAPI設計にも適しています。
2. Spring SecurityとJWTの関係とは?
Spring Securityは、Spring Frameworkで認証や認可を簡単に実装できるセキュリティ機能です。従来はセッションベースの認証が主流でしたが、JWTを組み合わせることでステートレスなAPI認証が可能になります。
Spring Securityでは、以下のような流れでJWT認証が行われます。
- ログインフォームでユーザーIDとパスワードを送信
- サーバーが認証に成功したら、JWTトークンを発行
- クライアントは受け取ったJWTをHTTPヘッダー(Authorization)に付けて以降のリクエストを送信
- サーバーはリクエストに含まれるJWTを検証し、ユーザー情報を取り出して認可を実施
以下はSpring SecurityでJWT認証を導入する際の基本的な構成図です:
- 認証用のコントローラ(@Controller)
- ユーザー情報を管理するサービス
- JWTトークンを生成・検証するユーティリティクラス
- Spring Securityのフィルターでトークンの検証処理
たとえば、以下のようなコントローラを用意して、ログインリクエストを受け付け、トークンを返すような処理になります。
@Controller
public class LoginController {
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpServletResponse response) {
// 認証処理(省略)
// JWTトークンを発行
String token = jwtUtil.generateToken(username);
// ヘッダーにトークンをセットして返却
response.setHeader("Authorization", "Bearer " + token);
return "loginSuccess";
}
}
このように、Spring SecurityとJWTを組み合わせることで、安全かつ効率的なログイン認証が可能になります。特にモバイルアプリやSPA(シングルページアプリ)と連携する場合には、セッションよりもJWTが推奨されます。
3. JWTトークンを発行するための基本的な流れ
JWTトークンを使った認証の流れを理解するには、「ログイン処理」から「トークンの生成・返却」までの一連の処理を確認することが大切です。ここでは、Spring SecurityとJWTを使ったトークン発行の具体的なステップを丁寧に解説します。
- ログイン画面からユーザーIDとパスワードを送信
- Spring Securityが認証処理を実行し、成功した場合に処理が進む
- ユーザー情報からトークンを生成する(JWTの発行)
- HTTPレスポンスのヘッダーやボディにトークンを設定してクライアントに返す
以下は、トークンを発行するための簡単なJWTユーティリティクラスの例です。
public class JwtUtil {
private final String SECRET_KEY = "mysecretkey";
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + 86400000); // 1日有効
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
このgenerateTokenメソッドは、ユーザー名を受け取ってトークンを生成します。トークンには有効期限(例:24時間)も設定されており、不正利用やセキュリティリスクを減らすために必須の設定です。
また、トークンはログイン成功時にレスポンスヘッダーやCookieなどでクライアントに渡されます。以下は前回のコントローラと合わせて使えるログイン処理の一部です。
@Controller
public class LoginController {
private final JwtUtil jwtUtil = new JwtUtil();
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpServletResponse response) {
// 認証成功したと仮定(実際はServiceでチェック)
String token = jwtUtil.generateToken(username);
response.setHeader("Authorization", "Bearer " + token);
return "loginSuccess";
}
}
このように、JWTトークンの発行処理は「認証 → トークン生成 → ヘッダー設定」というシンプルな構成で実現できます。Spring Securityと連携することで、より堅牢なセキュリティを確保できます。
4. JWTの中身とは?ペイロードや署名の意味を理解しよう
JWT(JSON Web Token)は、3つの部分に分かれています。それぞれの役割を理解することで、トークンの仕組みや安全性への理解が深まります。
xxxxx.yyyyy.zzzzz
- ヘッダー(Header)
トークンの種類や使用する署名アルゴリズムを指定します。一般的には以下のような内容が入ります。{ "alg": "HS256", "typ": "JWT" } - ペイロード(Payload)
ユーザーIDやロールなどの情報(クレーム)を含みます。ここに含める情報は、アプリケーションの認可に使うことができます。{ "sub": "user001", "name": "Taro Yamada", "iat": 1712100000, "exp": 1712186400 } - 署名(Signature)
ヘッダーとペイロードを暗号鍵で署名し、トークンが改ざんされていないことを保証します。これはSpring側で自動的に生成されます。
これら3つの情報は、ドット(.)で区切って1つの文字列になります。トークンはBase64エンコードされているため、専用のデコーダーを使えば中身を確認することも可能です(例:jwt.io)。
特に初心者が混乱しやすいのが「クレーム(Claim)」という用語です。これは、ペイロードに含まれる情報のことで、ユーザーのIDやロール、有効期限など、誰が・いつまで有効か・何ができるかといった情報を指します。
よく使われる標準クレームには以下のようなものがあります。
sub:トークンの対象(ユーザーID)iat:トークンの発行日時(issued at)exp:トークンの有効期限(expiration)
これらのクレームをもとに、Spring Securityでは認証と認可を実現します。
たとえば、「有効期限が切れているトークン」は自動的に認証エラーとなり、セキュリティ的に安全な状態が保たれます。
また、トークンの中にロール情報(ROLE_USERなど)を含めておけば、アクセス制御(認可処理)にも活用できます。
5. Spring Securityと連携したJWT発行処理の実装例
ここでは、Spring SecurityとJWTを連携させた具体的な実装例を紹介します。今回の前提として、@Controllerを使ってログイン処理を記述する点を忘れずに押さえてください。
認証用のサービスでユーザー認証を行い、認証が成功した場合にJwtUtilクラスを使ってトークンを発行します。以下がその基本的な実装です。
@Controller
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpServletResponse response) {
try {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(username, password);
authenticationManager.authenticate(authToken);
String token = jwtUtil.generateToken(username);
response.setHeader("Authorization", "Bearer " + token);
return "loginSuccess";
} catch (AuthenticationException e) {
return "loginError";
}
}
}
このように、Spring SecurityのAuthenticationManagerで認証し、JWTを発行する構成が基本となります。発行されたトークンはレスポンスヘッダーに設定され、以後のリクエストで使用可能となります。
6. トークンの保存・利用方法(Cookieやヘッダー)
JWTトークンは、ログイン後にクライアントに返却されますが、その保存方法にはいくつかの選択肢があります。代表的なのは以下の2つです。
- HTTPヘッダー(Authorization)に保存
- Cookieに保存
もっとも一般的なのは、Authorizationヘッダーに保存する方法です。以下のように設定して、リクエストごとにトークンを送信します。
Authorization: Bearer xxxxxx.yyyyy.zzzzz
一方で、Cookieに保存する方法もあります。セキュリティを考慮してHttpOnlyやSecure属性を使い、JavaScriptからアクセスできないようにするのが推奨されます。
Cookie jwtCookie = new Cookie("jwtToken", token);
jwtCookie.setHttpOnly(true);
jwtCookie.setSecure(true); // HTTPS環境の場合
jwtCookie.setPath("/");
response.addCookie(jwtCookie);
Cookieに保存する場合は、Spring SecurityのフィルターでCookieを取り出して検証する処理を追加する必要があります。
初心者におすすめなのは、まずはAuthorizationヘッダー方式です。扱いがシンプルで、開発やデバッグがしやすいという利点があります。
7. 初心者がつまずきやすいポイントと対策
JWTトークンの発行や認証は一見シンプルに見えますが、初心者がつまずきやすいポイントもいくつかあります。ここでは代表的な例とその対処法を紹介します。
このエラーは、主にAuthenticationManagerの設定ミスや、ユーザー情報の取得に失敗している場合に発生します。対策として、ユーザー情報の読み込みロジックやAuthenticationProviderの設定を見直しましょう。
この場合は、Spring Securityのフィルターでトークンの検証処理がうまく動作していない可能性があります。フィルターでAuthorizationヘッダーが正しく取得できているかを確認しましょう。
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// トークンの検証処理へ
}
JWTには有効期限(exp)があります。開発中に短い時間を設定していた場合、意図せず期限切れになってしまうことがあります。設定値と現在時刻の差を確認しましょう。
JWTの署名が一致しないと、トークンが改ざんされたとみなされ、認証エラーになります。SECRET_KEYの値が一致しているか確認してください。
こうしたつまずきを減らすためには、エラーメッセージをしっかりログに出力し、JWTライブラリの使い方を正確に把握することが重要です。また、Spring Securityの設定ファイルやSecurityFilterChainの記述ミスもトラブルの原因になるため注意が必要です。
初心者が安心して取り組むには、まずはログイン → トークン発行 → ヘッダー送信というシンプルな構成から始め、徐々に拡張していく方法がおすすめです。