JavaにおけるJWTの包括的な解説と実践ガイド
一、JWTとは何ですか?
JWT(JSON Web Token)は、JSON形式に基づくトークンで、クライアントとサーバー間で安全に情報をやり取りするために使用されます。主に認証と認可に利用され、特にフロントエンドとバックエンドが分離されたプロジェクトで広く採用されています。
JWTの構造
JWTは3つのセクションから構成されます:
- ヘッダー:JWTのメタデータを記述し、使用する署名アルゴリズムを示します。
- ペイロード:ユーザーの非機密情報(例:
ユーザーID、ユーザー名)を格納します。 - 署名:データの整合性を検証し、改ざんを防ぐためにヘッダーとペイロードを暗号化します。
JWTの例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywidXNlcm5hbWUiOiJqb2huZG9lIiwiaWF0IjoxNjkwOTY3NjAwLCJleHAiOjE2OTA5NzEyMDB9.aWQl3zjW7dK3WY3xOt3MzIlQbK4XkwnAs8fN8LS9hEI
デコード後の構造:
- ヘッダー:
{
"alg": "HS256",
"typ": "JWT"
}
- ペイロード:
{
"userId": 123,
"username": "johndoe",
"iat": 1690967600,
"exp": 1690971200
}
- 署名: ヘッダーとペイロードを指定されたアルゴリズム(例:HMAC SHA256)で暗号化して生成され、データの改ざんを検出します。
二、JWTの主要な特徴
- 無状態性:サーバー側でトークンの状態を保存する必要がなく、各リクエストでJWTを送信するだけで認証可能です。
- セキュリティ:署名メカニズムによりトークンの整合性を保証し、改ざんを防ぎます。
- 転送性:Base64エンコードにより転送が簡単です。
三、JWTの一般的な適用場面
- ユーザー認証:ログイン成功後、JWTを返して以降のリクエストで認証に使用します。
- APIアクセス制御:JWTに含まれるロール情報を基に、ユーザーが特定のインターフェースにアクセスできるかを判断します。
- フロントエンドとバックエンドの分離プロジェクト:HTTPヘッダー経由でJWTを送信し、認証プロセスを簡略化します。
四、JavaでのJWTの実装方法
以下はSpring Bootフレームワークとjjwtライブラリ(io.jsonwebtoken)を用いたJWTの生成、解析、検証の詳細です。
1. ディペンデンシーの追加
pom.xmlに以下の依存関係を追加します:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
2. JWTユーティリティクラスの作成
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.Claims;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private static final String SECRET_KEY = "mySecretKey"; // 署名用シークレット
private static final long EXPIRATION_TIME = 3600000; // 有効期限(ミリ秒)
/**
* JWTトークンの生成
*
* @param claims ユーザー情報を含むペイロード
* @return JWT文字列
*/
public static String createJWT(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date()) // 発行時刻
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 有効期限
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 署名アルゴリズム
.compact();
}
/**
* トークンの検証と解析
*
* @param token JWT文字列
* @return 解析結果
*/
public static Claims verifyJWT(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
/**
* トークンの有効期限チェック
*
* @param claims JWTペイロード
* @return 期限切れかどうか
*/
public static boolean isExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}
3. 実際のプロジェクトでの適用
ユーザー認証の実装
コントローラーの例:
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> authenticate(@RequestBody Map<String, String> credentials) {
String user = credentials.get("user");
String pass = credentials.get("pass");
// モック認証処理
if ("admin".equals(user) && "123456".equals(pass)) {
Map<String, Object> payload = new HashMap<>();
payload.put("user", user);
payload.put("role", "admin");
String jwt = JwtUtil.createJWT(payload);
Map<String, Object> response = new HashMap<>();
response.put("token", jwt);
return ResponseEntity.ok(response);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "認証失敗"));
}
}
}
APIアクセス制御
JWT検証インターセプターの設定:
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
try {
token = token.replace("Bearer ", "");
Claims payload = JwtUtil.verifyJWT(token);
request.setAttribute("payload", payload);
return true;
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
}
インターセプターの登録:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**") // 全てのパスを検査
.excludePathPatterns("/auth/login"); // ログインエンドポイントを除外
}
}