如今API愈來愈流行,如何安全保護這些API? JSON Web Tokens(JWT)能提供基於JSON格式的安全認證。它有如下特色:html
JWT易於辨識,是三段由小數點組成的字符串:java
aaaaaaaaaa.bbbbbbbbbbb.ccccccccccccgit
這三部分含義分別是header,payload, signaturegithub
頭部包含了兩個方面:類型和使用的哈希算法(如HMAC SHA256):web
{ "typ": "JWT", "alg": "HS256" }
對這個JSON字符進行base64encode編碼,咱們就有了首個JWT:算法
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9json
JWT的第二部分是payload,也稱爲 JWT Claims,這裏放置的是咱們須要傳輸的信息,有多個項目如註冊的claim名稱,公共claim名稱和私有claim名稱。api
註冊claim名稱有下面幾個部分:安全
公共claim名稱用於定義咱們本身創造的信息,好比用戶信息和其餘重要信息。服務器
私有claim名稱用於發佈者和消費者都贊成以私有的方式使用claim名稱。
下面是JWT的一個案例:
{ "iss": "scotch.io", "exp": 1300819380, "name": "Chris Sevilleja", "admin": true }
JWT第三部分最後是簽名,簽名由如下組件組成:
下面是咱們如何獲得JWT的第三部分:
var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); HMACSHA256(encodedString, 'secret');
這裏的secret是被服務器簽名,咱們服務器可以驗證存在的token並簽名新的token。
TWT支持的算法有:
============================================================================================================
以上是官網的理論部分,下面會有提供一些實例:
首先 導入 依賴:
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency>
1, 指定加密算法:
//HMAC Algorithm algorithmHS = Algorithm.HMAC256("secret");
-------------------------------------------------------------------------
//RSA
Map<String,Object> keys=RSAUtils.getKeys();
RSAPublicKey publicKey = (RSAPublicKey)keys.get("public"); //Get the key instance
RSAPrivateKey privateKey = (RSAPrivateKey)keys.get("private");//Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
2 , 生成token
用HS256生成token
try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }
用RS256生成token
Map<String,Object> keys=RSAUtils.getKeys(); RSAPublicKey publicKey = (RSAPublicKey)keys.get("public"); //Get the key instance RSAPrivateKey privateKey = (RSAPrivateKey)keys.get("private");//Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }
3, 驗證token
1)普通驗證
用HS256驗證token
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { Algorithm algorithm = Algorithm.HMAC256("secret"); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims }
用RS256驗證token
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims }
2)在payLoad 是能夠自定義數據,用於驗證,包括時間等。
在生成token的時候指定數據:
@Test public void gen1() throws IOException { String token =""; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //日期轉字符串 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND,30 ); //特定時間的年後 Date date = calendar.getTime(); try { Algorithm algorithm = Algorithm.HMAC256("mysecrite"); token = JWT.create() .withIssuer("auth0") .withSubject("xiaoming") .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) .withExpiresAt(date) .sign(algorithm); System.out.println("loglogagel:"+token); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. } }
驗證token是否過時,是否有制定的
@Test public void gen3(){ String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsImV4cCI6MTQ5NzY4NTQwOX0.DHY-90JAA63_TvI-gRZ2oHCIItMajb45zB1tdCHQ_NQ"; try { Algorithm algorithm = Algorithm.HMAC256("mysecrite"); JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(algorithm) .withIssuer("auth0") .withSubject("xiaomong"); Clock clock = new Clock() { @Override public Date getToday() { return new Date(); } };//Must implement Clock interface JWTVerifier verifier = verification.build(clock); DecodedJWT jwt = verifier.verify(token); System.out.println(jwt.getAlgorithm()); System.out.println(jwt.getType()); System.out.println(jwt.getIssuer()); System.out.println(jwt.getExpiresAt()); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported exception.printStackTrace(); } catch (JWTVerificationException exception){ //Invalid signature/claims exception.printStackTrace(); } }
若是 subject驗證的不一致,就會報以下錯誤:
若是時間超過 30 秒,會報以下錯誤:
對驗證的方法稍加修改:
@Test public void gen3(){ String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4aWFvbWluZyIsImFycmF5IjpbMSwyLDNdLCJpc3MiOiJhdXRoMCIsIm5hbWUiOiJJYW0gcmlnaHQgZnJvbSBjbGFpbSIsImV4cCI6MTQ5NzY4OTQ4NX0.6lsXISVAgi8B2wAvaZq4tj-h9Pgd6GGaOYZLz_gPFMU"; try { Algorithm algorithm = Algorithm.HMAC256("mysecrite"); JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(algorithm) .withIssuer("auth0") .withSubject("xiaoming"); Clock clock = new Clock() { @Override public Date getToday() { return new Date(); } };//Must implement Clock interface JWTVerifier verifier = verification.build(clock); DecodedJWT jwt = verifier.verify(token); Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name Claim claim = claims.get("name"); System.out.println(claim.asString()); //打印出claim的值 System.out.println(jwt.getAlgorithm()); System.out.println(jwt.getType()); System.out.println(jwt.getIssuer()); System.out.println(jwt.getExpiresAt()); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported exception.printStackTrace(); } catch (JWTVerificationException exception){ //Invalid signature/claims exception.printStackTrace(); }
驗證後的最後結果:
4,claim的添加,獲取
1) 內置的payload主要有如下幾個,若是沒有就返回null
Issuer ("iss") :發佈者
String issuer = jwt.getIssuer();
String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Date expiresAt = jwt.getExpiresAt();
Date notBefore = jwt.getNotBefore();
Date issuedAt = jwt.getIssuedAt();
String id = jwt.getId();
2)定義私有的claim
添加:
String token = JWT.create() .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) .sign(algorithm);
獲取:
JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) .build(); DecodedJWT jwt = verifier.verify("my.jwt.token");
目前,官方支持claim的類型的有:Boolean, Integer, Double, String, Date , String[] 和 Integer.
5, Header Claims
1)header claims 是定義header部分的內容,基本都是默認定義,不須要本身去設置的,內置的有:
String algorithm = jwt.getAlgorithm();
String type = jwt.getType();
String contentType = jwt.getContentType();
String keyId = jwt.getKeyId();
2)添加:
Map<String, Object> headerClaims = new HashMap(); headerClaims.put("owner", "auth0"); String token = JWT.create() .withHeader(headerClaims) .sign(algorithm);
3)獲取:
Claim claim = jwt.getHeaderClaim("owner");
總結: 看了其餘人的一些博客,發現他們的api都是相對老一點的版本,生成token是一步一步來,新的確實簡單方便不少。分享就這裏,歡迎交流。
補充參考連接:
web 中使用jwt: https://github.com/jwtk/jjwt
參考地址:https://github.com/auth0/java-jwt