JWT
JWT 概要
JWT(JSON Web Token)は、異なるシステム間でアイデンティティと認可関連の情報を渡すためによく使用される、コンパクトでURLセーフな宣言転送フォーマットです。
JWT 自体はトークン形式であり、ログインプロトコルやシングルサインオン方式とは同等ではありません。認証、認可、シングルサインオンプロセスにおけるトークンキャリアとして機能します。
JWTが機密かどうかはカプセル化方法に依存する。一般的な3 段階のJWTはJWSに属し、署名またはメッセージ認証コードのみで保護され、負荷内容は読み取ることができます。負荷はJWEを使用する場合にのみ暗号化されます。
JWTの一般的なアプリケーション
-
認証
ユーザがログインに成功すると、サーバはJWTを発行します。クライアントは後続のリクエストでトークンを運び、サーバは署名、有効期限、発行者、オーディエンスなどの情報を確認してトークンの信頼性を判断します。
-
アクセス許可
JWTは、ユーザID、ロール、パーミッション、スコープなどの宣言を運ぶことができます。サーバがトークンチェックを完了すると、これらの宣言に基づいて現在のユーザがターゲットリソースにアクセスできるかどうかを判断します。
-
システム間情報交換
送信者はJWTに署名し、受信者はデータが改ざんされているかどうか、トークンが信頼できる発行者によって生成されたかどうかを検証できる。コンテンツの機密性も要求される場合は、JWEまたは他の暗号化チャネルを使用してください。
-
シングルサインオン
統一アイデンティティ認証体系では、認証センターはJWTを発行し、各業務システムは約定に従ってトークンを検証することができる。JWTは、トークン発行、リフレッシュ、取り消し、信頼関係も処理する必要があるシングルサインオンスキームのコンポーネントに過ぎません。
JWTの特長
利点は
- Compact:HTTPリクエストヘッダーでの送信に適しています。
- クロス言語:JWTは標準化されたフォーマットであり、すべての主要言語に対応する実装があります。
- **分散認証が容易:リソースサーバは署名をローカルで検証できるため、集中型セッションストレージへの依存を減らすことができます。
- サポート宣言拡張:標準宣言に加えて、ビジネスカスタム宣言を追加できます。
制限は
- 発行されたトークンはすぐに取り消すことは容易ではない:通常、有効期間の短縮、ブラックリストの維持、トークンのバージョン番号の使用などのメカニズムが必要です。
- ペイロードは情報を漏洩する可能性があります:JWSのペイロードはBase 64 URLエンコーディングのみで、暗号化されません。
- トークンサイズが大きい場合があります:宣言が多すぎると、リクエストごとにネットワークオーバーヘッドが増加します。
- パーミッションの有効期限が切れる可能性があります:パーミッションをロングトークンに直接書き込むと、データベースパーミッションの変更はすぐに古いトークンに反映されません。
JWTの構成
一般的な署名 JWTは3つの部分で構成され、英語のピリオドで接続されます。
Header.Payload.Signature

Header
ヘッダーはJSONオブジェクトで、通常は以下のフィールドを含みます:
typ:トークンのタイプ、通常はJWT。alg:署名またはメッセージ認証コードアルゴリズム(例:HS256、RS256)。
例として:
{
"typ": "JWT",
"alg": "HS256"
}
ヘッダはBase 64 URLでエンコードされ、トークンの最初の部分を形成します。
Payload
Payloadは、クレームClaimを格納するために使用される。JWT 仕様は7つの登録宣言名を定義しており、これらのフィールドはすべてオプションです。
| 宣言 | 英文名称 | 作用 |
|---|---|---|
iss | Issuerさん | トークン発行者の識別 |
sub | Subject(件名) | トークントピックを識別し、通常はユーザーまたはサブジェクトを表す。 |
aud | Audience(オーディエンス) | トークンの予想される受信者の識別 |
exp | Expiration Timeシングル | トークンの有効期限の識別 |
nbf | Not Beforeシングル | 識別トークンがいつまで利用できないか |
iat | 問題が発生した | トークン発行時刻の識別 |
jti | JWT ID | トークンを識別する一意の番号 |
登録宣言に加えて、ビジネス·カスタム宣言を追加できます。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
暗号化されていないJWTペイロードにパスワード、銀行カード番号、ID 番号、鍵などの機密情報を保存しないでください。トークンを取得すると誰でもヘッダーとペイロードをデコードして読み取ることができます。
Signature
署名は、ヘッダーとペイロードが改ざんされていないことを確認し、トークンが対応する鍵を持つ当事者によって発行されたことを確認します。
HS256を例にとると、署名計算プロセスは次のように表現できる。
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
HMACを使用する場合、発行者は認証者と同じ鍵を共有します。RSAまたはECDSAを使用する場合、発行者は秘密鍵で署名し、認証者は公開鍵で署名します。
署名は完全性とソースの信頼性のみを保証し、ペイロードコンテンツを隠すことはできません。
Base64URL
Base 64 URLは、URLとHTTPヘッダの転送に適したBase 64バリアントです。
通常のBase 64と比較して、主に以下の処理を行います。
+を-に置き換えます。/を_に置き換えた。- 末尾の
=パディング文字を省略します。
Base 64 URLは暗号化アルゴリズムではなくエンコード方式です。
JJWTカプセル化ツールクラスを使用する
次の例は、JJWT 0.12.xに基づいています。構成エントリ内のキーはBase 64エンコードを使用し、デコード後に選択したHMACアルゴリズムの少なくともキー長要件を満たす必要があります。
設定例
jwt:
secret: ${JWT_SECRET}
expire-ms: 86400000
JWT_SECRETはコードリポジトリに直接コミットしないでください。環境変数または鍵管理サービスを通じて注入できます。
JWTツール·クラス
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.List;
@Component
public class JwtUtil {
private final SecretKey key;
private final long expireMs;
public JwtUtil(
@Value("${jwt.secret}") String secret,
@Value("${jwt.expire-ms}") long expireMs) {
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
this.expireMs = expireMs;
}
public String createToken(
Long userId,
String username,
Long roleId,
List<String> permissions) {
Date now = new Date();
Date expiration = new Date(now.getTime() + expireMs);
return Jwts.builder()
.subject(username)
.claim("userId", userId)
.claim("roleId", roleId)
.claim("permissions", permissions)
.issuedAt(now)
.expiration(expiration)
.signWith(key)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
public boolean isExpired(Claims claims) {
Date expiration = claims.getExpiration();
return expiration != null && expiration.before(new Date());
}
}signWith(key)は、鍵タイプと長さに基づいて互換性のある署名アルゴリズムを選択します。システムが固定アルゴリズムを必要とする場合は、発行と検証の両方で統一的に構成し、期待されるアルゴリズムのみを受け入れるように制限します。
Claimオブジェクト
ClaimsはJWTにおける宣言の集合を表す. Map<String, Object>のキーバリューアクセス機能を継承しながら、標準宣言を読み取る専用の方法を提供します。
一般的な方法は以下の通り
String subject = claims.getSubject();
Date issuedAt = claims.getIssuedAt();
Date expiration = claims.getExpiration();
String issuer = claims.getIssuer();
カスタム宣言を読み込むときは、ターゲットのタイプを指定できます。
Long userId = claims.get("userId", Long.class);
Long roleId = claims.get("roleId", Long.class);
ジェネリックコレクションの場合、Javaの型消去のため、直接読み取りでは通常生のListしか得られません。ビジネスレベルで個別に変換することも、カスタムデシリアライズスキームを使用することもできます。
@SuppressWarnings("unchecked")
List<String> permissions = claims.get("permissions", List.class);
テストトークンの作成と解決
次のテストでは、同じテストメソッドでトークンを作成して解析し、期限切れや改行で壊れたハードコードされたトークンを使用しません。
import io.jsonwebtoken.Claims;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@SpringBootTest
class JwtUtilTests {
@Autowired
private JwtUtil jwtUtil;
@Test
void shouldCreateAndParseToken() {
List<String> permissions = List.of(
"permission:query",
"permission:insert",
"permission:update",
"permission:delete",
"permission:check"
);
String token = jwtUtil.createToken(130L, "tom", 3L, permissions);
Claims claims = jwtUtil.parseToken(token);
assertEquals("tom", claims.getSubject());
assertEquals(130L, claims.get("userId", Long.class));
assertEquals(3L, claims.get("roleId", Long.class));
assertFalse(jwtUtil.isExpired(claims));
@SuppressWarnings("unchecked")
List<String> parsedPermissions = claims.get("permissions", List.class);
assertEquals(permissions, parsedPermissions);
}
}使用上の注意事項
- サーバは署名を検証する必要があり、ペイロードだけをデコードする必要があります。
expを検証し、iss、aud、nbfなどの宣言を業務上必要に応じて検証する必要があります。- クライアント自身が指定した任意のアルゴリズムを受け入れないでください。許可されるアルゴリズムとトークンの種類を制限してください。
- HMACキーは、キーの代わりに単純なパスワードを使用できないように、十分に長くランダムである必要があります。
- アクセストークンは短い有効期間を設定し、長期ログインが必要な場合はリフレッシュトークンメカニズムと連携してください。
- HTTPSを使用して、転送中にトークンが盗まれないように。
- クライアント側のストレージスキームは、XSS、CSRF、およびトークン漏洩からの保護に焦点を当てた脅威モデルに基づいて選択する必要があります。
気に入ったならばコメントを残してくださいね~