理解JSON Web Token (一)

综合技术 2018-12-09 阅读原文

一:理解单系统登录的原理及实现?

web应用采用的 browser/server 架构的,http是无状态协议的,也就是说用户从A页面跳转到B页面会发起http请求,当服务器返回响应后,当用户A继续访问其他页面的时候,服务器端无法获知该状态,因此会使用cookie/session来记录用户状态的。

session认证状态的基本原理:当客户端向服务器端请求时,会创建一个session标识存在客户端的cookie当中,每次请求的时候会将该标识随cookie一起发送到服务器端,服务器端会首先检查这个客户端的请求里面是否包含了一个session的标识,如果已经包含了,那么服务器端就会根据该session标识来判断用户的状态,否则的话,服务器端会创建一个新的session标识传给客户端的cookie当中,以后每次客户端请求的时候,会从cookie中获取该标识传递过去。

二:理解单点登录的原理?

上面使用session/cookie 可以实现单个系统的登录,那如果是多个系统的话,怎么办?难道需要用户一个个去登录? 一个个去注销?我们需要做的是,无论系统有多少个,我们只需要登录一次就够了,其他的相关的系统都可以登录/注销一次即可。

单系统登录解决方案的核心是cookie,cookie会携带服务器端返回的sessionId, 在浏览器与服务器端维护会话状态。但是我们知道cookie是有限制的,cookie有域的概念,浏览器发送http请求时会自动匹配该本站点的cookie域。而不是所有的cookie。

那么既然这样,我们很容易想到的是,我们可以把所有子系统的域名都放在一个顶级域名下不就可以了?比如 "*.taobao.com",然后将他们的cookie域设置为 "taobao.com", 但是这种并不好:

第一:因为所有系统的域名需要统一,比如淘宝和天猫的域名就不相同;

第二:应用群各个系统所使用的技术需要相同,比如tomcat服务器叫JESSIONID, 其他的服务器可能不叫这个标识。

第三:cookie的安全性不高的。

因此我们需要一种全新的方式来实现多系统应用群的登录,这就是单点登录。

什么是单点登录?单点登录的全称是 Single Sign On (可以简称为SSO), 在多个系统中只要登录一次,便可以在其他所有系统中得到授权而无需再次登录。

SSO有一个独立的认证中心,认证中心它可以接受用户的用户名密码等安全信息,其他的地方不接受登录入口,只接受认证中心的间接授权,间接授权它是通过令牌实现的。授权令牌作为参数会发送到各个子系统,子系统拿到令牌,因此会得到了授权,因此就可以创建了局部的会话。局部会话和单系统登录的原理很类似的。

下面我们来打个比方理解 单点登录的基本原理:

第一步:我想登录A系统,A系统发现用户未登录,因此我们需要他们跳转到SSO认证中心(且将自己请求的地址作为参数传递过去)。SSO认证中心发现未登录,会将用户引导到登录页面。

第二步:用户输入用户名和密码提交申请登录,SSO认证中心会检测用户名和密码信息,如果用户名和密码正确的话,那么用户和SSO认证中心之间会创建一个局部会话,并且创建一个授权令牌。sso认证中心会带着该令牌跳转到A系统那个请求的地址去。

第三步:A系统会检测该令牌,如果有效的话,就会跳到用户输入的地址页面去,否则,还是返回登录页面,提示错误信息。

第四步:用户访问系统B,系统B发现用户未登录,会跳转到SSO认证中心(将自己请求的地址作为参数传递过去),sso认证中心发现用户已经登录了,会跳转回系统B的那个地址去,并带上令牌,系统B拿到令牌,就会去sso认证中心去校验该令牌是否有效。

如果有效的话,说明认证成功了,就会跳转到系统B访问地址的页面上去。

用户现在已经登录成功了,sso认证中心会与各个子系统建立会话,用户与sso认证中心建立的会话被称为全局会话,用户与各个

子系统建立的会话被称为局部会话,局部会话建立之后,用户访问子系统资源后就不会再通过sso认证中心了。

三:什么是JSON Web Token?

JSON Web Token 是一个开放标准协议,它定义了一种紧凑和自包含的方式,它用于各方之间作为JSON对象安全地传输信息。

它有如下优点:

1. 可以适用于分布式的单点登录场景。

2. 可以使用跨域认证解决方案。

3. jwt实现自动刷新token的方案(待认证)。

JSON Web Token,它定义了一种紧凑和自包含的方式,如何理解紧凑和自包含呢?

紧凑:就是说这个数据量比较少,并且能通过url参数,http请求提交的数据以及http header的方式来传递。

自包含:这个串可以包含很多信息,比如用户id,订单号id等,如果其他人拿到该信息,就可以拿到关键业务信息。

3.1)JWT的基本原理,基本流程如下:

1. 客户端使用账号和密码请求登录接口。

2. 登录成功后服务器使用签名密钥生成JWT,然后返回JWT给客户端。

3. 客户端再次向服务端请求其他接口时会带上JWT。

4. 服务器接收到JWT后验证签名的有效性,对客户端做出相应的响应。

3.2)JWT与session的区别?

session是基于cookie来传输的,session信息是存储在服务器端中,客户端向服务器端发请求时,服务器端会返回一个jessionId给客户端中的cookie中,以后每次请求都会从cookie中的jessionid传递过去,服务器通过cookie中的sessionid获取到当前会话的用户,对于单系统来讲这是没有问题的,但是对于多个系统的话就涉及到session如何共享的问题了,并且随着认证用户增多的话,session会占用大量服务器内存。

JWT是存储在客户端的,服务器端不需要存储JWT,JWT含有用户id,服务器拿到jwt验证后就可以拿到用户信息了,jwt是无状态的,它不与任何机器绑定的,只要签名密钥足够的安全就能保证jwt的可靠性。

3.3)JWT中的token与session中的token安全性比较

session 中安全性问题:

服务器端执行session机制的时候会生成session的口令,在Tomcat服务器中,默认会采用 jsessionid 这个值,但是在其他服务器上会有所不同,比如Connect默认会叫 connect_uid, 我们一般把一些敏感的信息放在cookie中是不可取的,但是将口令放在cookie中还是可以的,如果口令被篡改了的话,就丢失了映射关系,也无法修改服务器端存在的数据,并且session的有效期一般为20/30分钟,如果在该时间之内客户端和服务器端没有产生任何交互,服务器端会自动将session自动清空,因此session中想要维护用户一直登陆的状态的话,需要客户端每隔20分钟使用setInterval自动发一个请求给服务器端,这样的话,前后端就有交互,所以就可以一直保持登陆状态。否则的话,每次20分钟后,登陆状态就会失效,每隔20分钟用户需要重新登录,用户体验将会变得不好。session这样做的最主要的是为了安全性考虑,有效期的时间非常短,防止黑客攻击。

JWT方案中安全性问题

jwt是存储在客户端的,服务器端不需要存储jwt的,客户端每次发送请求时会携带该token,然后到服务器端会验证token是否正确,是否过期了,然后会通过解码出携带的用户的信息的,但是如果token在传输的过程中被攻击者截取了的话,那么对方就可以伪造请求,利用窃取到的token模拟正常请求,实现用户的正常操作,而服务器端完全不知道,因为JWT在服务器端是无状态的,且服务器端不存储jwt的。其实jwt解决的问题是认证和授权的问题,对于安全性的话,还是建议对外公布的接口使用https.

四:理解JWT的基本数据结构

基本的JWT的数据结构是如下这样的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc1MzczNX0.h1XmQo017udxlFsH-8US9Lg8dJ0IDsSbRbjEN5Nq0l4

如上它是由三部分组成的,中间使用 . 分割成三个部分,它是有 Header(头部), PayLoad(负载),Signature(签名)组成的。

如:Header.Payload.Signature

4.1 Header

Header部分是一个JSON对象,描述JWT的元数据,一般是如下的样子:

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

如上json代码,alg属性表示签名的算法,默认是 HMAC SHA256 (缩写为:HS256); typ属性表示这个令牌(token)的类型为JWT. 最后将上面的JSON对象使用 Base64URL的算法转成字符串。

我们可以使用在线的base64编码转下( http://tool.oschina.net/encrypt?type=3 ),如下所示:

如上 alg 部分,默认加密的算法是 HMAC SHA256, 当然我们也可以选择下面的加密算法,加密算法有如下:

4.2 Payload

Payload部分也是一个JSON对象,用来存放实际需要传递的数据,官方提供了7个字段,如下:

iss(issuer): 签发人
exp (expiration time): 过期时间
sub (subject): 主题
aud (audience): 受众
nbf (Not Before): 生效时间
iat (Issued At): 签发时间
jti (JWT ID): 编号

payload的中文含义是载荷,它可以理解为存放有效信息的地方。这些有效信息一般包含如下三个部分:

4.2.1)标准中注册的声明:(如上就是官方提供的7个字段)。

4.2.2)公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但是不建议添加敏感信息,

因为该部分在客户单可解密。

4.2.3)私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,该部分信息

可以理解为明文信息。

那么定义一个简单的 payload 可以如下结构:

{
  "sub": '123456',
  "name": "kongzhi",
  "admin": true
}

我们还是使用如上的base64编码,会编码成如下所示:

4.3 Signature

Signature 是对前面两部分的签名,防止数据被篡改。签名是把Header和payload对应的json结构进行base64 编码之后得到的两个串用英文句点号拼接起来的,然后会根据header里面的alg指定的前面算法(默认是 HMAC SHA256)生成出来的。

如上header部分使用的是 HS256(即HMAC和SHA256),HMAC是用于生成摘要的,SHA256是用于对摘要进行数字签名的。因此使用HMACSHA256实现signature实现的算法如下:

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

如上是 Signature 签名算法,最后一个 secret 是加密的密钥的含义。因此通过如上的用法我们就可以拿到JWT了。

4.4 JWT实践

JWT的格式是由三个点分割的base64-URL字符串,可以在html或http环境中传递,我们可以简单的使用 https://jwt.io/ 官网来生成一个JWT了,如下是我前面定义的部分数据:

五:node中使用JWT的API

nodejs实现的jwt的github代码( https://github.com/auth0/node-jsonwebtoken )

它主要有3个方法:

5.1 jwt.sign(payload, secretOrPrivateKey, [options, callback])

payload 参数必须是一个object、Buffer、或 string.

注意:exp(过期时间) 只有当payload是object字面量时才可以设置。如果payload不是buffer或string,它会被强制转换为使用的字符串JSON.stringify()。

secretOrPrivateKey 参数是包含HMAC算法的密钥或RSA和ECDSA的PEM编码私钥的string或buffer。

options 参数有如下值:

algorithm:加密算法(默认值:HS256)
expiresIn:以秒表示或描述时间跨度zeit / ms的字符串。如60,"2 days","10h","7d",含义是:过期时间
notBefore:以秒表示或描述时间跨度zeit / ms的字符串。如:60,"2days","10h","7d"
audience:Audience,观众
issuer:Issuer,发行者
jwtid:JWT ID
subject:Subject,主题
noTimestamp
header

该方法如果是异步方法,则会提供回调,如果是同步的话,则将会 JsonWebToken返回为字符串。

在expiresIn, notBefore, audience, subject, issuer没有默认值时,可以直接在payload中使用 exp, nbf, aud, sub 和 iss分别表示。

注意:如果在jwts中没有指定 noTimestamp的话,在jwts中会包含一个iat,它的含义是使用它来代替实际的时间戳来计算的。

下面我们在项目中使用node中jsonwebtoken来生成一个JWT的demo了,在index.js 代码如下:

// 生成一个token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';

let token = jwt.sign({
  name: 'kongzhi'
}, secret, (err, token) => {
  console.log(token);
});

然后我们进入项目中的目录,执行 node index.js 执行后看到命令行中会打印中的token了,如下所示:

当然我们也可以设置token的过期时间,比如设置token的有效期为1个小时,如下代码:

// 生成一个token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';
// 设置token为一个小时有效期
let token = jwt.sign({
  name: 'kongzhi',
  exp: Math.floor(Date.now() / 1000) + (60 * 60)
}, secret, (err, token) => {
  console.log(token);
});

5.2 jwt.verify(token, secretOrPrivateKey, [options, callback])

该方法是验证token的合法性

比如上面生成的token设置为1个小时,生成的token为:

'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc2MjYzOSwiZXhwIjoxNTQzNzY2MjM5fQ.6idR7HPpjZIfZ_7j3B3eOnGzbvWouifvvJfeW46zuCw'

下面我们使用 jwt.verify来验证一下:

const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc2MjYzOSwiZXhwIjoxNTQzNzY2MjM5fQ.6idR7HPpjZIfZ_7j3B3eOnGzbvWouifvvJfeW46zuCw';
const secret = 'abcdef';
jwt.verify(token, secret, (error, decoded) => {
  if (error) {
    console.log(error.message);
  }
  console.log(decoded);
});

执行node index.js 代码后,生成如下信息:

现在我们再来生成一个token,假如该token的有效期为30秒,30秒后,我再使用刚刚生成的token,再去使用 verify去验证下,看是否能验证通过吗?(理论上token失效了,是不能验证通过的,但是我们还是来实践下)。如下代码:

// 生成一个token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';
// 设置token为30秒的有效期
let token = jwt.sign({
  name: 'kongzhi',
  exp: Math.floor(Date.now() / 1000) + 30
}, secret, (err, token) => {
  console.log(token);
});

在命令行中生成 jwt为: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImV4cCI6MTU0Mzc2MzU1MywiaWF0IjoxNTQzNzYzNTIzfQ.79rH3h_ezayYBeNQ2Wj8fGK_wqsEqEPgRTG9uGmvD64';

然后我们现在使用该token去验证下,如下代码:

// 生成一个token
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImV4cCI6MTU0Mzc2MzU1MywiaWF0IjoxNTQzNzYzNTIzfQ.79rH3h_ezayYBeNQ2Wj8fGK_wqsEqEPgRTG9uGmvD64';
const secret = 'abcdef';
jwt.verify(token, secret, (error, decoded) => {
  if (error) {
    console.log(error.message);
  }
  console.log(decoded);
});

执行命令,如下所示:

如上可以看到token的有效期为30秒,30秒后再执行的话,就会提示jwt过期了。

5.3 jwt.decode(token, [, options])

该方法是 返回解码没有验证签名是否有效的payload。

博客园-原创精华区

责编内容by:博客园-原创精华区阅读原文】。感谢您的支持!

您可能感兴趣的

Use MySQL JSON field in Laravel I have been working on an application which needs to store a lot of metadata about user profile, we discussed with the ...
ExpandableListView的完美实现,JSON数据源,右边自定义图片... 最近在项目中要使用ExpandableListView来实现一个下面这种效果 效果描述:这个要求的是点击一个时展开点击的哪一项,其余的都关闭(互斥效果),要实现点击时切换字体的颜色。 还没写的时候在网上看了一下,什么资源都...
Objective-C DRY JSON Mapping and creating objects I am trying to dynamically map JSON information into different objects. But I can't figure out quite how to get the w...
Creating Secure Password Resets With JSON Web Toke... Creating Secure Password Resets With JSON Web Tokens ByJamie Munro November 9th, 201...
QT解析和组装json json这个小朋友熟悉又陌生,今天给同学们好好讲讲QT是如何使用json的,一句话:简单 1、什么是json? A:json就是个字符串 ①一个json对象 {"name":"xupeidong","age":"18"} ...