JWT

如下内容 翻译、择抄、适当修改自 JWT官网,当了一次大天然的搬运工html


打开官网你就会看到这么一个介绍:java

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. JWT.IO allows you to decode, verify and generate JWTweb

翻译过来就是:算法

JWT是一个开源的、行业标准的RFC 7519方法,用于安全地声明双方。JWT.IO容许你解码,验证,生成JWT(JWT.IO是官网网页内嵌的一个JWT生成器)数据库



1. 什么是 JSON Web Token(JWT)


JWT是一个开源标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传递信息(此信息是一个JSON对象)。此信息是通过数字签名的,所以能够被验证和信任。JWT可使用密匙签名(兼用HMAC算法)或使用RSA或ECDSA的公用/专用密钥对来进行签名编程


尽管JWT能够进行加密以便在各方之间提供保密性,可是咱们将重点关注已签名的令牌(指JWT)。已签名的令牌能够验证其中声明的完整性,而加密的令牌的这些声明则对其余各方隐藏。当使用公钥/私钥对来对令牌进行签名时,签名还证实只有持有私钥的一方才是对令牌进行签名的一方(即身份认证)json



2. 咱们何时应该使用JWT


  • 受权:这是JWT的最多见用法。一旦用户登陆,每一个后续请求将包括JWT,从而容许用户访问该令牌容许的路由,服务和资源。单点登陆是当今普遍使用的一项功能,由于它的开销很小而且轻松跨域

  • 信息交换:JWT是在各方之间安全地传输信息的好方法。由于能够对JWT进行签名(例如,使用公钥/私钥对),因此您能够肯定发件人是他们所说的人。此外,因为签名是使用头部和有效负载计算的,所以您还能够验证内容是否遭到篡改


3. JWT的结构


JWT以紧凑的形式由三部分组成,这些部分由点 . 分隔,分别是:跨域

  • Header
  • Payload
  • Signature

所以,JWT一般以下所示浏览器

xxxxx.yyyyy.zzzzz安全


让咱们来分解不一样的部分:



3.1 Header(头部)

头部一般由两部分组成:令牌的类型和所使用的签名算法(如HMAC SHA256或RSA)


例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

而后,上面的JSON被Base64Url编码以造成JWT的第一部分



3.2 Payload(有效负载)

令牌的第二部分是有效负载,其中包含声明,而声明是有关实体的(一般是用户)和其余数据的声明,声明有三种类型:注册的、公共的、私有的


  • 注册声明(建议但不强制使用)
    • iss: 签发者
    • exp: 到期时间
    • sub: 主题
    • aud: 受众群众
    • jti: 身份标识(用于回避重放攻击)
    • others

请注意,声明名称仅是三个字符,由于JWT是紧凑的


  • 公开声明(能够添加任何信息,不建议添加敏感信息)

  • 私有声明(为共享信息而建立的自定义声明)

有效负载的事例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

而后,对有效负载进行Base64Url编码,以造成JSON Web令牌的第二部分


请注意,对于已签名的令牌,此信息尽管能够防止篡改,但任何人均可以读取。除非将其加密,不然请勿将机密信息放入JWT的有效负载或头部中



3.3 Signature(签名)

要建立签名部分,你必须获取编码后的头部编码后的有效负载密匙以及头部声明的加密算法,并对他们进行签名


例如:若要用HMAC SHA256算法,则将经过如下方式建立签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

签名用于验证消息在此过程当中没有更改,而且对于使用私钥进行签名的令牌,它还能够验证JWT的发送者是它所说的真实身份



3.4 放在一块儿组成JWT

输出是三个由点分隔的Base64-URL字符串,能够在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑


下面显示了一个JWT,它已对先前的标头和有效负载进行了编码,并用一个秘密进行了签名

base64UrlEncode(header) + . + base64UrlEncode(payload) + . + Signature

若是您想使用JWT并将这些概念付诸实践,则可使用jwt.io Debugger解码(官网的JWT编辑器),验证和生成JWT



4. JWT如何工做?

在身份验证中,当用户使用其凭据成功登陆时,将返回 JWT。因为令牌是凭据,所以必须格外当心以防止安全问题。一般,令牌的保留时间不该超过要求的时间


因为缺少安全性,你也不该该将敏感的会话数据存储在浏览器中


每当用户想要访问受保护的路由或资源时,用户代理一般应使用持有者模式,在HTTP请求头中设Authorization为JWT,请求头内容应以下所示:

Authorization: Bearer <token>

在某些状况下,这能够是无状态受权机制。服务器的受保护路由将在Authorization标头中检查有效的JWT ,若是存在,则将容许用户访问受保护的资源。若是JWT包含必要的数据,则能够减小查询数据库中某些操做的需求(好比用户名),尽管这种状况并不是老是如此


若是令牌是在Authorization请求头中发送的,则跨域资源共享(CORS)不会成为问题,由于它不使用cookie

可将JWT存于LocalStoage(我的补充)


请注意,使用签名的令牌,令牌中包含的全部信息都会暴露给用户或其余方,即便他们没法更改它。这意味着您不该将机密信息放入令牌中



5. 为何要使用JWT


因为JSON没有XML冗长,所以在编码时JSON也较小,从而使JWT比SAML更为紧凑。这使得JWT是在HTML和HTTP环境中传递的不错的选择


JSON解析器在大多数编程语言中都很常见,由于它们直接映射到对象。相反,XML没有天然的文档到对象映射。与SAML断言相比,这使使用JWT更加容易


关于用法,JWT是在Internet规模上使用的。这强调了在多个平台(尤为是移动平台)上对JSON Web令牌进行客户端处理的简便性


cookie+session这种模式一般是保存在服务器内存中,并且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只须要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析便可(我的补充)



6. 缺点(我的补充)

  • 注销后JWT还有效,因为JWT存放于客户端,用户点击注销后没法操做客户端的JWT,致使在JWT的过时时间前仍是有效,笔者的解决方法是在服务器端创建一个黑名单,在用户点击注销后将该用户放入黑名单,下次进入先去查看黑名单中是否存在该用户,这又和JWT背道而驰,在服务器端存储数据
  • 续签,若每次发现快过了有效期,则服务器端生成一个新的JWT发送给客户端,客户端检查新旧JWT不一致则替换


7. 简单事例

笔者就使用JWT官网排名靠前的java-jwt来举例说明了,觉得就一个包而没有使用maven和Springboot管理,一个个依赖独自去仓库下载,血的教训,那么列出所需的包

  • java-jwt-3.9.0
  • commons-codec-1.12
  • jackson-databind-2.10.0.pr3
  • jackson-annotations-2.10.0.pr3
  • jackson-core-2.10.0.pr3

定义JWT工具类

public class JWTUtil {
	
	// 密匙,本应从配置文件读取
	private static String secret = "1234567890";
	
	
	/**
	 * 建立一个JWT
	 * @param username
	 * @return token
	 */
	public static String createJWT(String username){
		
		// 过时多少秒
		long expSecond = 60 * 60;	// 一小时
		
		// 单纯为了使用1.8新时间API
		LocalDateTime nowLocalDateTime = LocalDateTime.now();
		LocalDateTime expLocalDateTime = nowLocalDateTime.plusSeconds(expSecond);
		
		// 时间戳
		Instant nowInstant = nowLocalDateTime.atZone(ZoneId.systemDefault()).toInstant();
		Instant expInstant = expLocalDateTime.atZone(ZoneId.systemDefault()).toInstant();
		
		// 转成jwt的date类型
		Date nowDate = Date.from(nowInstant);
		Date expDate = Date.from(expInstant);
		
		// 变量提高
		String token = null;
		try {
			token = JWT.create()
					.withIssuer("Howl")			// 签发者
					.withIssuedAt(nowDate)		// 签发时间
					.withExpiresAt(expDate)		// 到期时间
					.withClaim("username", username)	// 自定义声明
					.sign(Algorithm.HMAC256(secret));	// 签名
		} catch (JWTVerificationException  e) {
			System.out.println("Claim不能转成json或密匙无效将抛出JWTCreationException");
		}
		return token;
	}
	
	
	/**
	 * 验证token是否正确
	 * @param token
	 * @return T/F
	 */
	public static boolean verifyJWT(String token){
		
		JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
				.withIssuer("Howl")
				.build();		// 匹配指定的token发布者 Howl
		try {
			verifier.verify(token);		// 经过验证
		} catch (Exception e) {
			return false;				// 验证失败
		}
		return true;
	}
	
	
	/**
	 * 返回声明的集合,用来获取值
	 * @param token
	 * @return Claims集合
	 */
	public static Map getClaim(String token){
		
		// 解密
		JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
				.withIssuer("Howl")
				.build();
				
		// 解密后的JWT
		 DecodedJWT decodedJWT = verifier.verify(token);	
		
		return decodedJWT.getClaims();
	}
}

简单使用

public static void main(String[] args) {
		
		// 根据传入的username生成token
		String token = JWTUtil.createJWT("Howl");
		
		// 验证token是否正确
		if(JWTUtil.verifyJWT(token)){
			System.out.println("验证正确");
		}else{
			System.out.println("验证失败");
		}
		
		// 获取声明的集合
		Map<String,Claim> map = JWTUtil.getClaim(token);
		
		// 获取声明
		System.out.println(map.get("username").asString());		// 获取用户名
		System.out.println(map.get("exp").asDate());			//获取过时时间
		
}
验证正确
Howl
Tue Feb 11 11:06:56 GMT+08:00 2020


本文分享 CNBlog - Howlet。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索