首页 > 其他 > 详细

JWT的详细简介

时间:2019-11-26 22:08:06      阅读:132      评论:0      收藏:0      [点我收藏+]

技术分享图片

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由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的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或者资源:

技术分享图片

  1. 应用(或者客户端)想授权服务器请求授权。例如,如果用授权码流程的话,就是/oauth/authorize
  2. 当授权被许可以后,授权服务器返回一个access token给应用
  3. 应用使用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中不会存储任何用户信息。

没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。

虽然这一实现可能会有所不同,但其主要流程如下:

  1. 用户携带用户名和密码请求访问
  2. 服务器校验用户凭据
  3. 应用提供一个token给客户端
  4. 客户端存储token,并且在随后的每一次请求中都带着它
  5. 服务器校验token并返回数据

注意:

  1. 每一次请求都需要token
  2. Token应该放在请求header中
  3. 我们还需要将服务器设置为接受来自所有域的请求,用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的区别

  1. OAuth2是一种授权框架 ,JWT是一种认证协议
  2. 无论使用哪种方式切记用HTTPS来保证数据的安全性
  3. 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安装

pip install 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之外,还可以存储其他的和用户相关的信息,例如用户角色,用户性别等。

请求流程

  1. 用户使用用户名密码来请求服务器
  2. 服务器进行验证用户的信息
  3. 服务器通过验证发送给用户一个token
  4. 客户端存储token,并在每次请求时附送上这个token值
  5. 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持 CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin: *

JWT的结构

一个JWT是下面的结构

加密后jwt信息如下所示,是由.分割的三部分组成,分别为Header、Payload、Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT 的组成

  • Head -主要包含两个部分,alg指加密类型,可选值为HS256RSA等等,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 { compile ‘io.jsonwebtoken:jjwt:0.9.0‘ }

JWT依赖于Jackson,需要在程序中加入Jackson的jar包且版本大于2.x

签发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

总结

优点

  • 因为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

image_11120" src="/img/loading.gif" alt="">

什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准.该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

基于session认证所显露的问题

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

那么我们现在回到JWT的主题上。

JWT长什么样?

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

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

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

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

然后将其进行base64加密,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + ‘.‘ + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, ‘secret‘); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

如何应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch(‘api/user/1‘, {
  headers: {
    ‘Authorization‘: ‘Bearer ‘ + token
  }
})

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

技术分享图片

 

 

总结

优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

JWT的详细简介

原文:https://www.cnblogs.com/abdm-989/p/11938566.html

(0)
(0)
   
举报
评论 一句话评论(0
分享档案
最新文章
教程昨日排行
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!