什么是JWT?
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
jwt的组成
Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
Claims: Claims包含您想要签署的任何信息
JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明
示例
Header: { "alg": "HS256", "typ": "JWT" } Claims: { "sub": "1234567890", "name": "John Doe", "admin": true } Signature: base64UrlEncode(Header) + "." + base64UrlEncode(Claims)
加密生成的token:
有关JWT的详细介绍,请阅读这篇文章:
JJWT
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。
JJWT 规范兼容
创建和解析明文压缩JWTs
创建、解析和验证所有标准JWS算法的数字签名压缩JWTs(又称JWSs):
HS256:使用SHA-256的HMAC
HS384:使用SHA-384的HMAC
HS512:使用SHA-512的HMAC
RS256:使用SHA-256的RSASSA-PKCS-v1_5
RS384:使用SHA-384的RSASSA-PKCS-v1_5
RS512:使用SHA-512的RSASSA-PKCS-v1_5
PS256:使用SHA-256的RSASSA-PSS和使用SHA-256的MGF1
PS384:使用SHA-384的RSASSA-PSS和使用SHA-384的MGF1
PS512:使用SHA-512的RSASSA-PSS和使用SHA-512的MGF1
ES256:使用P-256和SHA-256的ECDSA
ES384:使用P-384和SHA-384的ECDSA
ES512:使用P-521和SHA-512的ECDSA
下面我们根据 https://github.com/jwtk/jjwt 上的demo,来介绍下 JJWT 的用法。
jjwt 安装
jjwt 提供了 Maven 和 Gradle 两种构建方式,Maven 配置如下即可使用 JJWT。
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
Gradle 使用方式如下:
dependencies { compile 'io.jsonwebtoken:jjwt:0.9.0' }
注意:JJWT依赖于Jackson 2.x. 如果您已经在您的应用程序中使用了旧版本的 Jackson 请升级相关配置。
示例代码
签发 JWT
public static String createJWT() { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setId(id) // JWT_ID .setAudience("") // 接受者 .setClaims(null) // 自定义属性 .setSubject("") // 主题 .setIssuer("") // 签发者 .setIssuedAt(new Date()) // 签发时间 .setNotBefore(new Date()) // 失效时间 .setExpiration(long) // 过期时间 .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙 return builder.compact(); }
验证 JWT
public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); }
完整示例
package com.tingfeng.demo; import com.google.gson.Gson; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.tomcat.util.codec.binary.Base64; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { /** * 由字符串生成加密key * * @return */ public SecretKey generalKey() { String stringKey = Constant.JWT_SECRET; // 本地的密码解码 byte[] encodedKey = Base64.decodeBase64(stringKey); // 根据给定的字节数组使用AES加密算法构造一个密钥 SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 创建jwt * @param id * @param issuer * @param subject * @param ttlMillis * @return * @throws Exception */ public String createJWT(String id, String issuer, String subject, long ttlMillis) throws Exception { // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的时间 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的) Map<String, Object> claims = new HashMap<>(); claims.put("uid", "123456"); claims.put("user_name", "admin"); claims.put("nick_name", "X-rapido"); // 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。 // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 SecretKey key = generalKey(); // 下面就是在为payload添加各种标准声明和私有声明了 JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body .setClaims(claims) // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setId(id) // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setIssuedAt(now) // iat: jwt的签发时间 .setIssuer(issuer) // issuer:jwt签发人 .setSubject(subject) // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .signWith(signatureAlgorithm, key); // 设置签名使用的签名算法和签名使用的秘钥 // 设置过期时间 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } return builder.compact(); } /** * 解密jwt * * @param jwt * @return * @throws Exception */ public Claims parseJWT(String jwt) throws Exception { SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样 Claims claims = Jwts.parser() //得到DefaultJwtParser .setSigningKey(key) //设置签名的秘钥 .parseClaimsJws(jwt).getBody(); //设置需要解析的jwt return claims; } public static void main(String[] args) { User user = new User("tingfeng", "bulingbuling", "1056856191"); String subject = new Gson().toJson(user); try { JwtUtil util = new JwtUtil(); String jwt = util.createJWT(Constant.JWT_ID, "Anson", subject, Constant.JWT_TTL); System.out.println("JWT:" + jwt); System.out.println("\n解密\n"); Claims c = util.parseJWT(jwt); System.out.println(c.getId()); System.out.println(c.getIssuedAt()); System.out.println(c.getSubject()); System.out.println(c.getIssuer()); System.out.println(c.get("uid", String.class)); } catch (Exception e) { e.printStackTrace(); } } }
Constant.java
package com.tingfeng.demo; import java.util.UUID; public class Constant { public static final String JWT_ID = UUID.randomUUID().toString(); /** * 加密密文 */ public static final String JWT_SECRET = "woyebuzhidaoxiediansha"; public static final int JWT_TTL = 60*60*1000; //millisecond }
输出示例
JWT:eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIxMjM0NTYiLCJzdWIiOiJ7XCJuaWNrbmFtZVwiOlwidGluZ2ZlbmdcIixcIndlY2hhdFwiOlwiYnVsaW5nYnVsaW5nXCIsXCJxcVwiOlwiMTA1Njg1NjE5MVwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiWC1yYXBpZG8iLCJpc3MiOiJBbnNvbiIsImV4cCI6MTUyMjMxNDEyNCwiaWF0IjoxNTIyMzEwNTI0LCJqdGkiOiJhNGQ5MjA0Zi1kYjM3LTRhZGYtODE0NS1iZGNmMDAzMzFmZjYifQ.B5wdY3_W4MZLj9uBHSYalG6vmYwdpdTXg0otdwTmU4U 解密 a4d9204f-db37-4adf-8145-bdcf00331ff6 Thu Mar 29 16:02:04 CST 2018 {"nickname":"tingfeng","wechat":"bulingbuling","qq":"1056856191"} Anson 123456
一般我们把验证操作作为中间件或者拦截器就行了,请求后端API接口时候,通过过滤器获取header中的token,验证是否正确、是否过期等。
参考文章
jjwt-gitHub:https://github.com/jwtk/jjwt