JWT解读

一.什么是JWT:

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,以JSON对象的形式在各方之间安全地传输信息。由于该信息是数字签名的,因此可以验证和信任它。JWTs可以使用秘钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

虽然JWTs也可以加密以提供各方之间的机密性,但是我们将重点讨论签名令牌。签名的令牌可以验证其中包含的声明的完整性,而加密的令牌可以向其他方隐藏这些声明。当令牌使用公钥/私钥对签名时,签名还证明只有持有私钥的一方才是签名的一方。

优点:是在分布式系统中,很好地解决了单点登录问题,很容易解决了session共享的问题。 
缺点:是无法作废已颁布的令牌/不易应对数据过期。

二.使用JWT的场景:

1.Authorization(授权)

这是使用JWT最常见的场景。一旦用户登录,随后的每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用JWT的一个特性,因为它的开销小,而且能够跨域访问(这是cookie做不到的)。

具体思路如下:

客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。此后,客户端将在与服务器交互中都会在请求头中携带JWT,服务端(通常是拦截器或过滤器)使用自己保存的key计算、验证签名以判断该JWT是否可信。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization(当然该字段可以自定义)字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。

2.Information Exchange(信息交换):

JWT是在各方之间安全传输信息的好方法。因为JWTs可以签名——例如,使用公钥/私钥对——所以你可以确保发送方是他们所说的人。此外,由于签名是使用header和payload计算的,您还可以验证内容有没有被篡改。善用JWT有助于减少服务器请求数据库的次数。

三.JWT的结构:

一个token分3部分,按顺序为

  • Header
  • Payload
  • Signature

3部分之间用“.”号做分隔。例如

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJBUFAiLCJ1c2VyX2lkIjoiMTEiLCJpc3MiOiJTZXJ2aWNlIiwiZXhwIjoxNTQ1Mjc1NzAxLCJpYXQiOjE1NDQ0MTE3MDF9.8pekdPk2ORBFem2D7VD3g0mhWduNQA2vNyNDG_rWuBw

Header(头部)

标头通常由两部分组成:令牌的类型和正在使用的散列算法(如HMAC SHA256或RSA)。如:

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

然后,将这个JSON编码为Base64Url,形成JWT的第一部分

Payload(有效载荷)

令牌的第二部分是有效载荷,主要包含实体(通常是用户)和其他数据的声明。声明有三种类型:registered, public, and private

1.Registered claims(注册声明)

详细可以参考:https://tools.ietf.org/html/rfc7519#section-4.1

这些是一组预定义的声明,它们不是强制使用的,而是推荐使用的,以提供一组有用的、可互操作的声明。其中包括:iss(issuer 发行者)、exp(expiration time 过期时间)、sub(subject 主体)、aud(audience 受众)等7个,如下。

iss: jwt签发者

sub: jwt所面向的用户,可以用来存放用户id

aud: 接收jwt的一方

exp: jwt的过期时间,这个过期时间必须要大于签发时间

nbf: 定义在什么时间之前,该jwt都是不可用的.

iat: jwt的签发时间

jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

注意,只要JWT要求是紧凑的,声明的名称就能是三个字符(即三个字母)长。

2.Public claims(公开声明)

这些可以由使用JWTs的用户随意定义。但是为了避免冲突,应该在IANA JSON Web Token Registry表中定义它们,或者将它们定义为包含抗冲突namespace的URI。

3.Private claims(私有声明):

这些是自定义声明,用于在同意使用这些声明的双方之间共享信息,这些既不是注册声明,也不是公共声明。

一个有效载荷示例可以是:

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

然后对payload进行Base64Url编码,形成JWT的第二部分

请注意,对于已签名的令牌,该信息虽然受到保护,不受篡改,但任何人都可以读取(读取后通过base64解码即可得到明文)。除非经过加密,否则不要将秘密信息放在JWT的payloadheader中。

Signature(签名)

要创建签名部分,您必须获取编码的头编码的有效载荷秘钥(secret)、头中指定的算法,并对其签名。

例如,如果您想使用HMAC SHA256算法,签名将以以下方式创建:

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

signature 用于验证消息在此过程中没有更改,并且,对于使用私钥签名的令牌,它还可以验证JWT的发件人就是它说的那个人。

更具体的理解:未签名的令牌base64url编码的header和payload拼接而成(使用"."分隔),签名则通过私有的key计算而成,最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用"."分隔)就是完整的JWT了:

key = 'secretkey' //秘钥  
unsignedToken = base64UrlEncode(header) + '.' + base64UrlEncode(payload)  //未签名token
signature = HMACSHA256(unsignedToken,key) //签名
//完整的token
token = base64UrlEncode(header) + '.' + base64UrlEncode(payload) + '.' + base64UrlEncode(signature) 

最后,JWT是由被"."分割的三个Base64-URL字符串组成,该字符串可以在HTML和HTTP环境中轻松传递,同时与基于xml的标准(如SAML)相比更紧凑。

如果您想使用JWT并将这些概念付诸实践,可以使用jwt.io Debugger来解码、验证和生成JWT。

Base64URL算法

该算法和常见Base64算法类似,稍有差别。

作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换,这就是Base64URL算法,很简单把。

具体可以查看我发的一篇文章:base64原理与运用

四.JWT的工作原理:

在身份验证中,当用户成功地使用其用户名/密码登录时,将返回JWT。由于JWT是凭据,因此必须非常小心地防止安全问题。一般来说,不要保存超过所需时间的令牌。

当用户希望访问受保护的路由或资源时,用户代应该在请求中携带JWT,通常在请求头中携带,建议命名为Authorization,比如

Authorization: jwt字符串

在某些情况下,这可能是一种无状态授权机制。服务器检查(通常自己定义一个拦截器/过滤器检查)Authorization header请求头中是否存在有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,还可以减少对数据库的查询。如果令牌在请求头中发送,则跨源资源共享(Cross-Origin Resource Sharing, CORS)不会成为问题,因为它不使用cookie。

下图显示了如何获得JWT并使用它访问api或资源:

请注意,使用带签名的令牌时,令牌中包含的所有信息都将公开给用户或其他方,即使他们无法更改它。这意味着你不应该将秘钥信息放在令牌中。

五.为什么要使用JWT

让我们讨论JWT与Simple Web Tokens (SWT) 和Security Assertion Markup Language Tokens (SAML)相比的优点。

1.紧凑:由于JSON比XML更简洁,因此在编码时,它的大小也更小,这使得JWT比SAML更紧凑。这使得JWT成为在HTML和HTTP环境中传递的一个很好的选择。

2.可以有多种算法,安全:SWT只能由使用HMAC算法的共享秘钥对称地签名。JWT和SAML令牌可以使用X.509证书形式的公钥/私钥对进行签名。但是,与JSON的简单性相比,使用XML数字签名签名XML而不引入模糊的安全漏洞是非常困难的。

3.json容易解析:JSON解析器在大多数编程语言中很常见,因为它们直接映射到对象。相反,XML没有自然的文档到对象映射。这使得使用JWT比使用SAML更容易。

4.适合在移动端使用:在使用方面,JWT是按Internet规模使用的。这突出了JWT在多个平台(尤其是移动平台)上的客户端处理的易用性。

六.使用JWT管理session的缺点

1.更多的空间占用。如果将原存在服务端session中的各类信息都放在JWT中保存在客户端,可能造成JWT占用的空间变大,需要考虑cookie的空间限制等因素,如果放在Local Storage,则可能受到XSS攻击。

2.更不安全。这里是特指将JWT保存在Local Storage中,然后使用Javascript取出后作为HTTP header发送给服务端的方案。在Local Storage中保存敏感信息并不安全,容易受到跨站脚本攻击,跨站脚本(Cross site script,简称xss)是一种“HTML注入”,由于攻击的脚本多数时候是跨域的,所以称之为“跨域脚本”,这些脚本代码可以盗取cookie或是Local Storage中的数据。可以从这篇文章查看XSS攻击的原理解释。

3.无法作废已颁布的令牌。所有的认证信息都在JWT中,由于在服务端没有状态,即使你知道了某个JWT被盗取了,你也没有办法将其作废。在JWT过期之前(你绝对应该设置过期时间),你无能为力。

 

七.实战

1.引入依赖

<!-- =================JWT集成================= -->
<dependency>
	<groupId>com.auth0</groupId>
	<artifactId>java-jwt</artifactId>
	<version>3.4.0</version>
</dependency>

<!-- JJWT(jst的框架):它是为了更友好在JVM上使用JWT,是基本于JWT, JWS, JWE, JWK框架的java实现。 -->
<!-- 诞生原因:原始JWT的API不够有好,不停的用withClaim放数据 -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.0</version>
</dependency>

2.登录生成jwt代码

private static final String SECRET = "companiontek";//秘钥
	/**
	 * @param staffId 用户id
	 * @param phone   用户手机号
	 * @param expirationTime  过期时间
	 * @return  登录时生成token
	 */
	public static String createJavaWebToken(String staffId, String phone, Date expirationTime) {
		//生成登录令牌
		return Jwts.builder().setSubject(staffId).claim("phone", phone).setIssuedAt(new Date())
				.setExpiration(expirationTime).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact();

	}

private static Key getKeyInstance() {
		//We will sign our JavaWebToken with our ApiKey secret
		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
		byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
		Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
		return signingKey;
	}

3.过滤器/拦截器根据token获取用户id

   /**
     * 解析Token返回staffId,当验证失败返回null
     *
     * @param request
     * @return
     */
    public static Integer parserStaffIdByToken(HttpServletRequest request) {
        try {
            String token = request.getHeader("token");
            final Claims claims = Jwts.parser().setSigningKey(SECRET)
                    .parseClaimsJws(token).getBody();
            return Integer.parseInt(claims.getSubject());

        } catch (Exception e) {
        	
        }
        return null;
    }

参考资料:

10分钟了解JSON Web令牌(JWT)

JWT缺点

JWT全面解读、使用步骤

猜你喜欢

转载自blog.csdn.net/u010825931/article/details/84940844
jwt