ããã¯ããªã«ãããããŠæžãããã®ïŒ
- æè¿ãã¡ãã£ãšJWTã«ã€ããŠç¥ããªããšãããªããªããšæãã§ãããšããããŸããŠ
- è£ã®ä»çµã¿ãšããŠJWTã䜿ã£ãŠããã®ãããã®ã§ãããããå°ãJWTèªäœã«åãåã£ãŠã¿ãããš
- ãªã«ãããJWTãæ±ããã©ã€ãã©ãªã䜿ã£ãŠè©Šãã€ã€ãæèŠãæŽãã§ã¿ã
ãšããããšã«ããJWTãæ±ã£ãŠã¿ãããšãã話ã
Java JWT
JWTãæ±ãã®ã«ã¯ãJava JWTãšããã©ã€ãã©ãªãéžãã§ã¿ãŸããã
GitHub - jwtk/jjwt: Java JWT: JSON Web Token for Java and Android
jwt.ioã«ãèŒã£ãŠããã©ã€ãã©ãªã§ãGitHubã®ã¹ã¿ãŒæ°ãå€ãããªãšã
ãããããJWTèªäœã¯ïŒ
仿§ãèªããã
JWTによるJSONに対する電子署名と、そのユースケース | DevelopersIO
ãã®ããããèªãã§ããŠãJWTïŒJSON Web TokenïŒãJWSïŒJSON Web SignatureïŒãJWEïŒJSON Web EncryptionïŒã®
é¢ä¿ãã¡ãã£ãšãããæãã§ãããã
JSON Web Token (JWT)
ã¯ã¬ãŒã ã®ã»ãããJSONãªããžã§ã¯ããšããŠæåå衚çŸã«ããŠJWSãJWEã«ãšã³ã³ãŒãããããšã§, ã¯ã¬ãŒã ã«å¯Ÿããããžã¿ã«çœ²åãMACãšæå·åã®äž¡æ¹ãå¯èœã«ãªã.
JSON Web Signature (JWS)
ããžã¿ã«çœ²åãããã¯MACåãããã¡ãã»ãŒãžã衚çŸããããŒã¿æ§é . JWSã¯ä»¥äžã®3ã€ã®å€ããæ§æããã: JWS ããã, JWS ãã€ããŒã, JWS 眲å
仿§æžã®JWTã®äŸãèŠãŠãããšãJSONã§ããJWTããããJWTã¯ã¬ãŒã ã»ã»ããããJWSãäœãããŠããããŸã
ããããŸããã
Java JWTã®ãSigned JWTsããèŠããšããã®æ§åãã³ãŒãã§æžãããŠããã®ã§ããã¡ãã®æ¹ãæèŠãã€ãã¿ããããã
ãããŸããã
JWSãæ§æããæååã¯ãBase64ã§ç°¡åã«ãã³ãŒãã§ããJWTããããJWTã¯ã¬ãŒã ã»ã»ããã眲åãšãªããå
容èªäœã¯èª°ã§ãèªããã
ãšããæãã§ããïŒæ¹ãã鲿¢ã¯çœ²åã§ã§ããïŒã
Java JWTã䜿ã£ãŠã¿ã
ãšããããã§ããããŸã§å眮ãã«ããŠJava JWTã§ã¡ãã£ãšéãã§ã¿ãŸãããã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã
$ java -version openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-1ubuntu0.18.04.1-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) $ mvn -version Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-39-generic", arch: "amd64", family: "unix"
æºå
MavenäŸåé¢ä¿ã¯ããã¡ãã
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.10.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.10.5</version> <scope>runtime</scope> </dependency>
jjwt-apiãšjjwt-implã¯ãšããããå¿
èŠãªã®ãšãJSONãæ±ãããã®ã©ã€ãã©ãªãå¿
èŠã§ããJava JWTã¯JacksonãšJSON-Javaã®
ã·ãªã¢ã©ã€ã¶ãŒïŒãã·ãªã¢ã©ã€ã¶ãŒãæäŸããŠããããã®ã©ã¡ããã䜿ãããšã«ãªããŸãã
ãªããjjwt-api以å€ã¯ãããããã¹ã³ãŒãã¯runtimeã§OKã§ãã
ããšã¯ããã¹ãã³ãŒãã§ç¢ºèªããã®ã§ããã¹ãã©ã€ãã©ãªã远å ã
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.11.1</version> <scope>test</scope> </dependency>
ãã¹ãã³ãŒãã®é圢
ãã¹ãã³ãŒãã®é圢ã¯ããã¡ãã
src/test/java/org/littlewings/jjwt/JjwtTest.java
package org.littlewings.jjwt; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.crypto.spec.SecretKeySpec; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SignatureException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class JjwtTest { // ããã«ããã¹ããæžãïŒïŒ }
ããã«ããã¹ããè¶³ããŠãããããšæããŸãã
ç°¡åãªJava JWTã®ãµã³ãã«
ãšãããããæžããŠã¿ãã®ã¯ãããªæãã
@Test public void gettingStarted() { String secretKey = UUID.randomUUID().toString(); byte[] secretKeyAsBytes = secretKey.getBytes(StandardCharsets.UTF_8); String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .signWith(Keys.hmacShaKeyFor(secretKeyAsBytes)) .compact(); System.out.println("JWT = " + jwsString); Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(Keys.hmacShaKeyFor(secretKeyAsBytes)) .parseClaimsJws(jwsString); System.out.println("parsed JWT = " + parsedJws); assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256"); }
ç§å¯éµã¯ã©ã³ãã UUIDã§é©åœã«çæãJWTã¯ã¬ãŒã ã»ã»ããã¯subjectã®ã¿ã§ãã
JWTãäœã£ãŠããã®ã¯ããã®éšåã§ãã
String jwsString =
Jwts.builder()
.setSubject("磯éã«ããª")
.signWith(Keys.hmacShaKeyFor(secretKeyAsBytes))
.compact();
ã¢ã«ãŽãªãºã ã¯ãéµã®é·ãã«åãããŠéžæããŠããããŸããã
éžæè¢ã¯ããã®ããŒãžã§ã³ã ãšãHmacSHA512ãããHmacSHA384ãããHmacSHA256ãããéžã°ããŸãã
çæãããJWTã¯ããããªæãã«ãªããŸãã
JWT = eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLno6_ph47jgqvjg4TjgqoifQ.wHj5Yg1P9lh4MXQuf5ViXqgHeA9thpZ0vsvfIGAeZ80
ããŒã¹ããŠã¿ãŸãããã
Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(Keys.hmacShaKeyFor(secretKeyAsBytes)) .parseClaimsJws(jwsString);
JW"S"ãšããŠããŒã¹ããŸãã
System.out.printlnããŠã¿ããšããããªæãã«ãªã£ãŠããŸãã
parsed JWT = header={alg=HS256},body={sub=磯éã«ããª},signature=wHj5Yg1P9lh4MXQuf5ViXqgHeA9thpZ0vsvfIGAeZ80
äžèº«ã確èªã
assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256");
ä»åã®ç§å¯éµã§ã¯ããHmacSHA256ãã¢ã«ãŽãªãºã ãéžæãããããã§ãïŒJWTäžã¯ãHS512ãïŒã
Java JWTã®ãµã³ãã«ã§ã¯ãç§å¯éµãèªåçæãããµã³ãã«ã«ãªã£ãŠããã®ã§ããããã¡ãã¯ãŸãããããªãšã
â»ã¢ã«ãŽãªãºã ã¯æç€ºããŠãã
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();
ã¢ã«ãŽãªãºã ãæç€ºãã
å ã»ã©ã¯ã¢ã«ãŽãªãºã ãã©ã€ãã©ãªåŽã«éžãã§ããã£ãŠããŸããããä»åºŠã¯æç€ºããŠã¿ãŸãã
@Test public void specifiedAlgorithm() { String secretKey = UUID.randomUUID().toString(); byte[] secretKeyAsBytes = secretKey.getBytes(StandardCharsets.UTF_8); String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact(); System.out.println("JWT = " + jwsString); Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString); System.out.println("parsed JWT = " + parsedJws); assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256"); }
SecretKeySpecã䜿ã£ãŠãæå®ããŸããã
String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact();
ããã¯ãå ã»ã©ã®ã©ã€ãã©ãªåŽã§éžãã§ããã£ãŠããå Žåã«ãå éšçã«äœ¿ãããŠããæ¹æ³ãšåããªã®ã§ãããâŠã
JWTãšãããŒã¹ããçµæã¯ãã¡ãã§ãã
JWT = eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLno6_ph47jgqvjg4TjgqoifQ.zr8C4O7IlDpHrnr-8gcEUtlFTMihFL0GAGppGklGhx4 parsed JWT = header={alg=HS256},body={sub=磯éã«ããª},signature=zr8C4O7IlDpHrnr-8gcEUtlFTMihFL0GAGppGklGhx4
JWTã«æå¹æéãèšå®ãã
ç¶ããŠãJWTã«æå¹æéãèšå®ããŠã¿ãŸãããã
@Test public void expired() { String secretKey = UUID.randomUUID().toString(); byte[] secretKeyAsBytes = secretKey.getBytes(StandardCharsets.UTF_8); long timeout = 1 * 1000; // 1 sec String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .setExpiration(new Date(System.currentTimeMillis() + timeout)) .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact(); System.out.println("JWT = " + jwsString); Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString); System.out.println("parsed JWT = " + parsedJws); assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256"); try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { // ignore } assertThatThrownBy(() -> Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString) ) .isInstanceOf(ExpiredJwtException.class) .hasMessageContaining("JWT expired at"); }
æå¹æéã¯ãDateã§æå®ããŸããä»åã¯ãæå¹æéã1ç§ã«ããŠã¿ãŸãã
long timeout = 1 * 1000; // 1 sec String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .setExpiration(new Date(System.currentTimeMillis() + timeout)) .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact();
ãããããšãJWTãäœæããçŽåŸã¯ããŒã¹ã§ããŸãã
Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString); System.out.println("parsed JWT = " + parsedJws); assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256");
å°ãåŸ ã€ãšãæå¹æéåãã§äŸå€ãã¹ããŒãããŸãã
try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { // ignore } assertThatThrownBy(() -> Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString) ) .isInstanceOf(ExpiredJwtException.class) .hasMessageContaining("JWT expired at");
çæãããJWTãšãããŒã¹çµæã¯ãã¡ãã
JWT = eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLno6_ph47jgqvjg4TjgqoiLCJleHAiOjE1NDIyOTQ0NzJ9.jaio2KTNJ-OQdMJ-qe9mhOv6LFrvA8PKEz6rvW10Tx0 parsed JWT = header={alg=HS256},body={sub=磯éã«ããª, exp=1542294472},signature=jaio2KTNJ-OQdMJ-qe9mhOv6LFrvA8PKEz6rvW10Tx0
眲åãã¡ãã£ãšããã£ãŠã¿ã
JWTïŒãšãããJWSïŒãæ§æããŠãããæåŸã®èŠçŽ ã§ãã眲åãå°ãããã£ãŠãæ€èšŒã§ãšã©ãŒã«ãªãããšã確èªããŠã¿ãŸãã
@Test public void invalidKey() { String secretKey = UUID.randomUUID().toString(); byte[] secretKeyAsBytes = secretKey.getBytes(StandardCharsets.UTF_8); String jwsString = Jwts.builder() .setSubject("磯éã«ããª") .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact(); System.out.println("JWT = " + jwsString); assertThatThrownBy(() -> Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString.substring(0, jwsString.length() - 1)) ) .isInstanceOf(SignatureException.class) .hasMessage("JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted."); }
JWTã1æååã£ãã®ã§ã眲åãåããªããªãäŸå€ãã¹ããŒãããŸããã
JWTã¯ã¬ãŒã ã»ã»ãããèšå®ãã
æåŸã«ãJWTã¯ã¬ãŒã ã»ã»ãããèšå®ããŠã¿ãŸãã
以äžã®ããã«ãClaimsã䜿ãããšã§Mapã®ããã«ïŒãšããããMapã®å®è£
ã«ãªã£ãŠããã®ã§ããïŒãä»»æã®é
ç®ãèšå®
ããããšãã§ããŸãã
@Test public void setClaims() { String secretKey = UUID.randomUUID().toString(); byte[] secretKeyAsBytes = secretKey.getBytes(StandardCharsets.UTF_8); Claims claims = Jwts.claims(); claims.put(Claims.SUBJECT, "磯éã«ããª"); claims.put(Claims.ISSUER, "foo"); claims.put("mydata", "Hello!!"); String jwsString = Jwts.builder() .setClaims(claims) .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact(); System.out.println("JWT = " + jwsString); Jws<Claims> parsedJws = Jwts.parser() .setSigningKey(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .parseClaimsJws(jwsString); System.out.println("parsed JWT = " + parsedJws); assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getBody().getIssuer()).isEqualTo("foo"); assertThat(parsedJws.getBody().get("mydata")).isEqualTo("Hello!!"); assertThat(parsedJws.getBody().toString()).isEqualTo("{sub=磯éã«ããª, iss=foo, mydata=Hello!!}"); assertThat(parsedJws.getHeader().getAlgorithm()).isEqualTo("HS256"); }
ãããªæãã§ããã
Claims claims = Jwts.claims(); claims.put(Claims.SUBJECT, "磯éã«ããª"); claims.put(Claims.ISSUER, "foo"); claims.put("mydata", "Hello!!"); String jwsString = Jwts.builder() .setClaims(claims) .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact();
ããšããšæšæºçã«æ±ºãŸã£ãŠãããã®ïŒStandard ClaimsïŒã«ã€ããŠã¯ã以äžã®ããã«ã¡ãœãããçšæãããŠãããããŸãã
String jwsString = Jwts.builder() .setSubject("...") .setIssuer("...") .signWith(new SecretKeySpec(secretKeyAsBytes, "HmacSHA512")) .compact();
Claimsã«å¯ŸããŠããããŒãå®çŸ©ããããããŠãŸãããã
Claims claims = Jwts.claims(); claims.put(Claims.SUBJECT, "磯éã«ããª"); claims.put(Claims.ISSUER, "foo"); claims.put("mydata", "Hello!!");
getterãããããã§ããã
assertThat(parsedJws.getBody().getSubject()).isEqualTo("磯éã«ããª"); assertThat(parsedJws.getBody().getIssuer()).isEqualTo("foo");
Mapã®ããŒããBuilderã«å¯ŸããŠçŽæ¥èšå®ããããšãã§ããã®ã§ããã®ãããã¯ããã¥ã¡ã³ããåç §ãããšããã§ãããã
åæ§ã®ããšããJWTããããŒã«ãèšããŸãã
ãã®ãµã³ãã«ã§ãçæãããJWTãšããŒã¹çµæã¯ããã¡ãã§ãã
JWT = eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLno6_ph47jgqvjg4TjgqoiLCJpc3MiOiJmb28iLCJteWRhdGEiOiJIZWxsbyEhIn0.r2Ew-mOe5G05x_1_Y6qM7FeTEpteev_Rc2YKNjAOOZM parsed JWT = header={alg=HS256},body={sub=磯éã«ããª, iss=foo, mydata=Hello!!},signature=r2Ew-mOe5G05x_1_Y6qM7FeTEpteev_Rc2YKNjAOOZM
ãªããšãªããé°å²æ°ã¯ã€ãããã®ã§ãããã§è¯ããšããŸãããã