1. JSON Web Token是什么
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
2. 什么时候你应该用JSON Web Tokens
下列场景中使用JSON Web Token是很有用的:
Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
3. JSON Web Token的结构是什么样的
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
因此,一个典型的JWT看起来是这个样子的:
xxxxx.yyyyy.zzzzz
接下来,具体看一下每一部分:
Header
header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。
例如:
然后,用Base64对这个JSON编码就得到JWT的第一部分
Payload
JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。
Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
Public claims : 可以随意定义。
Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
下面是一个例子:
对payload进行Base64编码就得到JWT的第二部分
注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
Signature
为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。
例如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
看一张官网的图就明白了:
4. JSON Web Tokens是如何工作的
在认证的时候,当用户用他们的凭证成功登录以后,一个JSON Web Token将会被返回。此后,token就是用户凭证了,你必须非常小心以防止出现安全问题。一般而言,你保存令牌的时候不应该超过你所需要它的时间。
无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。
header应该看起来是这样的:
Authorization: Bearer <token>
服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。
如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。
下面这张图显示了如何获取JWT以及使用它来访问APIs或者资源:
应用(或者客户端)想授权服务器请求授权。例如,如果用授权码流程的话,就是/oauth/authorize
当授权被许可以后,授权服务器返回一个access token给应用
应用使用access token访问受保护的资源(比如:API)
5. 基于Token的身份认证 与 基于服务器的身份认证
5.1. 基于服务器的身份认证
在讨论基于Token的身份认证是如何工作的以及它的好处之前,我们先来看一下以前我们是怎么做的:
HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证
传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。
这种基于服务器的身份认证方式存在一些问题:
Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
CSRF : 用户很容易受到CSRF攻击。
5.2. JWT与Session的差异
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
5.3. 基于Token的身份认证是如何工作的
基于Token的身份认证是无状态的,服务器或者Session中不会存储任何用户信息。
没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。
虽然这一实现可能会有所不同,但其主要流程如下:
用户携带用户名和密码请求访问
服务器校验用户凭据
应用提供一个token给客户端
客户端存储token,并且在随后的每一次请求中都带着它
服务器校验token并返回数据
注意:
每一次请求都需要token
Token应该放在请求header中
我们还需要将服务器设置为接受来自所有域的请求,用 Access-Control-Allow-Origin: *
5.4. 用Token的好处
无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!
还有一点,token在一段时间以后会过期,这个时候用户需要重新登录。这有助于我们保持安全。还有一个概念叫token撤销,它允许我们根据相同的授权许可使特定的token甚至一组token无效。
5.5. JWT与OAuth的区别
OAuth2是一种授权框架 ,JWT是一种认证协议
无论使用哪种方式切记用HTTPS来保证数据的安全性
OAuth2用在使用第三方账号登录的情况(比如使用weibo, qq, github登录某个app),而JWT是用在前后端分离 , 需要简单的对后台API进行保护时使用。
5.6. 关于OAuth可以参考下面几篇
《OAuth 2.0 》
《Spring Security OAuth 2.0 》
《OAuth 2.0 授权码请求 》
6. 参考
https://jwt.io/
https://scotch.io/tutorials/the-ins-and-outs-of-token-based-authentication#toc-why-tokens-came-around
https://tools.ietf.org/html/rfc7519#section-3
http://blog.leapoahead.com/2015/09/06/understanding-jwt/
https://cnodejs.org/topic/557844a8e3cc2f192486a8ff
http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/
感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐” 按钮,您的“推荐” 将是我最大的写作动力! 欢迎各位转载,但必须在文章页面中给出作者和原文链接!
jwt原理(json web token)
我们之前是使用session实现登录,通过实际密码+盐组成字符串进行md5存入redis或者数据库中,输入的密码与实际校验通过,发送给客户端一个有效时间的token,期间可以实现登录访问了
而jwt无需通过redis或者存储在数据库中了,直接通过一串字符串就能拿到信息和时间
优势 ‘‘‘
解决跨域问题:这种基于Token的访问策略可以克服cookies的跨域问题。
服务端无状态可以横向扩展,Token可完成认证,无需存储Session。
系统解耦,Token携带所有的用户信息,无需绑定一个特定的认证方案,只需要知道加密的方法和密钥就可以进行加密解密,有利于解耦。
防止跨站点脚本攻击,没有cookie技术,无需考虑跨站请求的安全问题。
‘‘‘
劣势
‘‘‘
jwt一旦发送不能撤回发送的信息
不能防止CSRF攻击等
‘‘‘
jwt格式分为三部分组成,header,playload,sign
header就是json格式 {"typ":"JWT","alg":"HS256","exp":1491066992916},typ表示类型为jwt格式,alg表示加密方式为hs256,exp表示时间
playroad是请求体,根据业务自行定义,可以是一个字典一个列表,一个字符串
sign表示签名的生成 加密原理:
把header和playload分别使用base64url编码,接着用‘ . ‘ 把两个编码后的字符串连接起来,再把这拼接起来的字符串配合密钥进行HMAC SHA -256算法加密,最后再次base64编码下,这就拿到了签名sign. 最后把header和playload和sign用‘ . ‘ 连接起来就生成了整个JWT
解密原理:
后端服务校验jwtToken是否有权访问接口服务,进行解密认证,如校验访问者的userid,首先用将字符串按.号切分三段字符串,分别得到header和playload和sign。然后将header.playload拼装用密钥和HAMC SHA-256算法进行加密然后得到新的字符串和sign进行比对, 如果一样就代表数据没有被篡改,然后从头部取出exp对存活期进行判断,如果超过了存活期就返回空字符串,如果在存活期内返回userid的值
校验原理:
整个jwt的结构是由header.playload.sign连接组成,只有sign是用密钥加密的,而所有的信息都在header和playload中可以直接获取,sign的作用只是校验header和playload的信息是否被篡改过,所以jwt不能保护数据,但以上的特性可以很好的应用在权限认证上
itsdangerous是python基于jwt的实现的,我们可以用第三方库来实现
itsdangerous安装
Signer(secret_key, salt=None, sep=‘.‘, key_derivation=None, digest_method=None, algorithm=None)
# 签名加密/解密
from itsdangerous import Signer
s = Signer(‘ rainbol ‘ ) # 设置盐值
res = s.sign(‘ me ‘ ).decode() # 设置签名
print (res) # me.OJZBr7m8IGARJ0qzK07wdy9xAJM 返回一个签名.字符串
print (s.unsign(‘ me.OJZBr7m8IGARJ0qzK07wdy9xAJM ‘ ).decode()) # 解签 返回me ,如果中间任意字符串不正确都会报错
TimestampSigner(secret_key, salt=None, sep=‘.‘, key_derivation=None, digest_method=None, algorithm=None)
# 带时间签名加密/解密 from itsdangerous import TimestampSigner
s = TimestampSigner(‘ rainbol ‘ ) # 设置盐值
string = s.sign(‘ 123 ‘ ) # 加签
print (string.decode()) # 返回123.XNkPLg.Qr0E6jJvj7-tg40SBtC0kunkM1w
res = s.unsign(string, max_age=20) # 解签 max_age设置过期时间20秒
print (res.decode()) # 返回123 ,如果中间任意字符串不正确或者超过max_age时间都会报错
Serializer(secret_key, salt=‘itsdangerous‘, serializer=None, signer=None, signer_kwargs=None)
# 序列化
from itsdangerous import Serializer
l = Serializer(‘ rainbol ‘ ) # 设置盐值
re = l.dumps([‘ 1314 ‘ , ‘ 2233 ‘ ])
print (re) # ["1314","2233"].V2vY21tK5Nc8wYP6rlY9-F2zhH0
print (l.loads(re)) # [‘1314‘, ‘2233‘] 如果不正确会报错
# 如果需要加入时间戳,需要加入TimedSerializer,用法和TimestampSigner一致
# url安全序列化
from itsdangerous import URLSafeSerializer
l = URLSafeSerializer(‘ rainbol ‘ )
re = l.dumps([‘ 122 ‘ , ‘ 2233 ‘ ]) # 返回 WyIxMjIiLCIyMjMzIl0.SujNVMlTr3RTkuQIHIRjl1R7tTs 序列化更加安全,其他一致
# jsonweb签名
from itsdangerous import JSONWebSignatureSerializer
s = JSONWebSignatureSerializer(‘ rainbol ‘ ) # 设置盐值
res = s.dumps({‘ rainbol ‘ : ‘ 123456 ‘ }) # 设置加密字典
print (res.decode()) # eyJhbGciOiJIUzUxMiJ9.eyJyYWluYm9sIjoiMTIzNDU2In0.CCMq-9G8HkoMb7NFXHeTSg0iG2lndsyZL-fcrV85gfuXBekNP3AWa3bUTYWWHuiFRsueqZ495S5bMxFCoglxZg
res = s.loads(‘ eyJhbGciOiJIUzUxMiJ9.eyJyYWluYm9sIjoiMTIzNDU2In0.CCMq-9G8HkoMb7NFXHe ‘
‘ TSg0iG2lndsyZL-fcrV85gfuXBekNP3AWa3bUTYWWHuiFRsueqZ495S5bMxFCoglxZg ‘ , return_header=True) # 解密字典
print (res) # 返回{‘rainbol‘: ‘123456‘} 如果中间任意字符串不正确都会报错
在实际开发过程中用以下方法即可
# 带有时间的jsonweb签名
import itsdangerous
salt = ‘ saO)(&)H ‘ # 设置盐值
t = itsdangerous.TimedJSONWebSignatureSerializer(salt, expires_in=600) # 指定参数,第一个是盐值,第二个是TTL加密过期时间
res = t.dumps({‘ username ‘ : ‘ rainbol ‘ , ‘ password ‘ : ‘ 123456 ‘ }) # 设置加密的字典
print (res.decode()) # 取加密信息
session = ‘ eyJhbGciOiJIUzUxMiIsImlhdCI6MTU1NzcyNzM4NywiZXhwIjoxNTU3NzI3OTg3fQ.eyJ1c2VybmFtZSI6InJhaW5ib2wiLCJwYXNzd29yZCI6IjEyMzQ1NiJ9.5r2_YYH06HLCW9Ix7EB5UGpk0wPD8RmWib0EoD9lgZIaRHEaH-qrMfQeKPriQNplD1gNLMM2Dn9NX-zoSz20Gg ‘
res = t.loads(session)# 解析加密信息,如果加密信息不正确会报错
print (res)
参考https://blog.csdn.net/weiker12/article/details/68950279
版权声明:本文原创发表于 博客园,作者为 RainBol 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
1.什么是JWT?
JWT全称JSON Web Token。是为了在网络应用环境键传递声明而执行的一种基于JSON的开放标准。
2.JWT的使用场景?
授权:一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。单点登录是一种在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。 因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。 此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
3.JWT的结构组成?
由三部分组成,用( .
)进行分隔,他们是
头部(header)
有效载荷(payload)
签名(signature)
因此,JWT通常如下表示
xxxxxx.yyyyy.zzzzz
头部:
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。然后,这个json被编码称Base64Url,形成JWT的第一部分。
有效载荷:
有效载荷是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sup:主题
aud:用户
nbf:在此之前不可用
iat:发行时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存,形成JWT第二部分。
签名:
签名是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要制定一个密钥(secret)。该密钥仅仅为保存在服务器中,并且不能向用户公开。然后,使用头部中指定的签名算法(默认情况下为HMACSHA256)根据以下公式生成签名。
在计算出签名后,将JWT头部,有效载荷和签名的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象,返回给用户。
4.JWT的用法?
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,如下面格式
{
"姓名" : "张三" ,
"角色" : "管理员" ,
"到期时间" : "2018年7月1日0点0分"
}
以后用户与服务端通信的时候,都要返回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
客户端收到服务器返回的JWT,可以存储到Cookie里面,也可以存储到localStorage。
此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在cookie里面自动发送,但是这样就不能实现跨域。所以更好的做法是放在HTTP请求的头信息Authorization字段里面
另一种做法是,跨域的时候,JWT就放在POST请求的数据里面。
5.JWT特点?
(1)JWT默认是不加密的,但也是可以加密的。生成原始Token以后,可以用密钥在加密一次。
(2)JWT不加密的情况下,不能将秘密数据写入JWT。
(3)JWT不仅可以用于认证,也可以死用于交换信息。有效使用JWT,可以降低服务器的查询数据库的次数。
(4)JWT最大的缺点是,用于服务器不保存sessi000on状态,因此无法在使用过程中废止某个token,或者更改token的权限,也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期限应该设置的比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了较少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输。
7.JWT的入门案例(Java方式)
创建一个项目,先引入依赖。
创建一个Jwtutil工具类,里面有包括创建Jwt和解析Jwt
创建JWT
public String createJWT(String id, 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<String,Object>();
claims.put("uid", "DSSFAWDWADAS..."); claims.put("user_name", "admin"); claims.put("nick_name","DASDA121");
/** * 生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 */ SecretKey key = generalKey(); //下面就是在为payload添加各种标准声明和私有声明了 //这里其实就是new一个JwtBuilder,设置jwt的body JwtBuilder builder = Jwts.builder() //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(id) //iat: jwt的签发时间 .setIssuedAt(now) //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .setSubject(subject) //设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, key); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); //设置过期时间 builder.setExpiration(exp); } //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt return builder.compact(); } 解析JWT
public Claims parseJWT(String jwt) throws Exception{ //签名秘钥,和生成的签名的秘钥一模一样 SecretKey key = generalKey(); //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(jwt).getBody(); //claims相当于一个map,包含了我们想要的信息 return claims; }
接下来测试一下,首先创建一个jwt,得到一个xxxxxx.zzzzzz.yyyyy字符串
我们再来解析一下这个字符串:
这样,我们就能够根据这个字符串解析出用户信息,从而验证登录授权等功能。
什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
简单来说就是 JWT(Json Web Token)是实现token技术的一种解决方案
为什么使用JWT
token验证和session认证的区别
传统的session认证
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
session缺点
基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户
Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。
基于session认证暴露的问题
Session 需要在服务器保存,暂用资源
扩展性 session认证保存在内存中 ,无法扩展到其他机器中
CSRF 基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如用户角色,用户性别等。
请求流程
用户使用用户名密码来请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持 CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin: *
。
JWT的结构
一个JWT是下面的结构
加密后jwt信息如下所示,是由.分割的三部分组成,分别为Header、Payload、Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT 的组成
Head -主要包含两个部分,alg指加密类型,可选值为HS256
、RSA
等等,typ=JWT
为固定值,表示token的类型
Header :
{
"alg" : "HS256" ,
"typ" : "JWT"
}
Payload - Payload又被称为Claims包含您想要签署的任何信息
Claims:
{
"sub" : "1234567890" ,
"name" : "John Doe" ,
"admin" : true
}
JWT Payload的组成
Payload通常由三个部分组成,分别是 Registered Claims ; Public Claims ; Private Claims ;每个声明,都有各自的字段。
Registered Claims
iss 【issuer】发布者的url地址
sub 【subject】该JWT所面向的用户,用于处理特定应用,不是常用的字段
aud 【audience】接受者的url地址
exp 【expiration】 该jwt销毁的时间;unix时间戳
nbf 【not before】 该jwt的使用时间不能早于该时间;unix时间戳
iat 【issued at】 该jwt的发布时间;unix 时间戳
jti 【JWT ID】 该jwt的唯一ID编号
Signature 对 则为对Header、Payload的签名
Signature :
base64UrlEncode(Header) + "." + base64UrlEncode(Claims)
头部、声明、签名用 . 号连在一起就得到了我们要的JWT 也就是夏明这种类型的字符串
eyJhbGciOiJIUzI1NiJ9.
eyJleHAiOjE1MTUyOTgxNDEsImtleSI6InZhdWxlIn0.
orewTmil7YmIXKILHwFnw3Bq1Ox4maXEzp0NC5LRaFQ
其实这些事一行的,我只是让看的更直白点将其割开了。
JAVA 实现
JAVA中使用JWT
使用Maven引入和Gradle引入
Maven
<dependency >
<groupId > io.jsonwebtoken</groupId >
<artifactId > jjwt</artifactId >
<version > 0.9.0</version > </dependency >
Gradle
dependencies
JWT依赖于Jackson,需要在程序中加入Jackson的jar包且版本大于2.x
签发JWT
public static String createJWT () {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(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 {
public SecretKey generalKey () {
String stringKey = Constant.JWT_SECRET;
byte [] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key = new SecretKeySpec(encodedKey, 0 , encodedKey.length, "AES" );
return key;
}
public String createJWT (String id, String issuer, String subject, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Map<String, Object> claims = new HashMap<>();
claims.put("uid" , "123456" );
claims.put("user_name" , "admin" );
claims.put("nick_name" , "X-rapido" );
SecretKey key = generalKey();
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(id)
.setIssuedAt(now)
.setIssuer(issuer)
.setSubject(subject)
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0 ) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
}
public Claims parseJWT (String jwt) throws Exception {
SecretKey key = generalKey();
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt).getBody();
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 ;
}
输出示例
JWT:eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIxMjM0NTYiLCJzdWIiOiJ7XCJuaWNrbmFtZVwiOlwidGluZ2ZlbmdcIixcIndlY2hhdFwiOlwiYnVsaW5nYnVsaW5nXCIsXCJxcVwiOlwiMTA1Njg1NjE5MVwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiWC1yYXBpZG8iLCJpc3MiOiJBbnNvbiIsImV4cCI6MTUyMjMxNDEyNCwiaWF0IjoxNTIyMzEwNTI0LCJqdGkiOiJhNGQ5MjA0Zi1kYjM3LTRhZGYtODE0NS1iZGNmMDAzMzFmZjYifQ.B5wdY3_W4MZLj9uBHSYalG6vmYwdpdTXg0otdwTmU4U
解密
a4d9204f-db37-4 adf-8145 -bdcf00331ff6
Thu Mar 29 16 :02 :04 CST 2018
{"nickname" :"tingfeng" ,"wechat" :"bulingbuling" ,"qq" :"1056856191" }
Anson
123456
总结
优点
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息, 所以它易于应用的扩展
安全相关
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
保护好secret私钥,该私钥非常重要。
如果可以,请使用https协议
参考文章
http://www.ibloger.net/article/3075.html
jjwt-gitHub:https://github.com/jwtk/jjwt
https://blog.csdn.net/u012240455/article/details/79019825
https://blog.csdn.net/u012017645/article/details/53585872
http://itindex.net/detail/58305-jwt-json-web
http://itindex.net/detail/58305-jwt-json-web
http://itindex.net/detail/58629-json-web-token
https://www.jianshu.com/p/d215e70dc1f9
欢迎访问我的github github博客地址
http://wsccoder.top/2018/08/22/Jwt/
https://github.com/wsccoder