Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519).该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。
从 HTTP 谈起
HTTP 是一个无状态的应用层协议。
- 无状态意味着每个请求都是独立的。
- 每次请求都不不清楚之前的请求发生了什么。
好比我们在淘宝上网购:
- 登录淘宝
- 下单购买
- 支付金额
然而,因为 HTTP 是无状态的,我们在下单购买的时候,服务器此时已经不记得我们的身份信息了,需要重新进行身份认证。
传统跨域登录认证方式
传统的登录校验采用的是 cookie-seession 方式,它的流程一般如下:
- 客户端使用用户名和密码向服务器请求登录。
- 服务器验证成功后,在当前对话(session)里面保存相关的数据(用户 UID、登录时间等)。
- 服务器向用户返回一个 session_id,写入用户的 cookie。
- 用户之后的每一次请求,都会通过 cookie,将 session_id 提交给服务器。
- 服务器收到 session_id 后,查找之前保存的对话数据,获取用户身份,从而判断用户是否已经完成登录校验。
传统模式的缺点是可扩展性差。对于单机操作影响不大,但如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session,而 session 一般是采用文件的方式存储,这会带来文件同步问题和文件读取问题。即便将 session 放入到 redis 中,也无法避免高并发数据的读取问题。
JWT 的验证方式
JWT 采用的是 token 令牌的方式进行校验:
- 客户端使用用户名和密码向服务器请求登录。
- 服务器验证成功后,签发一个 token 返回给客户端。
- 客户端收到 token 后将其缓存起来,之后的每次请求都会携带该 token。、
- 用户之后的每一次请求,都会通过 cookie,将 session_id 提交给服务器。
- 服务器收到请求后,验证请求中携带的 token,验证通过后进行业务逻辑处理。
这样做的优点在于:
- 服务器不保存任何 session 数据,减少服务器开销,方便扩展。
- JWT 构成简单,只占用很少的字节,便于传输。
- JSON 格式通用,便于跨语言使用。
一个 JWT 字符串包括以下三个部分:
-
头部(header)
-
标识用于生成签名的算法,例如:
{ ‘typ‘: ‘JWT‘, ‘alg‘: ‘HS256‘ }
-
typ 表示 token 的类型,默认为 JWT,一般不改动。
-
alg 表示加密算法,默认 HMAC SHA256。
-
-
载荷(payload)
- 存放有效信息:
- 官方标准的字段申明
- 公共的字段声明
- 私有的字段声明
- 存放有效信息:
-
签证(signature)
- 用密钥对 header 和 playload 两部分进行签证,防止数据被篡改。
Django REST framework
我们的项目后端是基于 Django 进行开发的。
一般而言是在 restful 的接口中使用 JWT token,相关的 Django 库是 djangorestframework
和 djangorestframework-jwt
。
首先需要在 settings
中设置 DEFAULT_AUTHENTICATION_CLASSES
来进行用户认证:
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': (
'rest_framework.schemas.coreapi.AutoSchema'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
)
}
接着设置 JWT_AUTH 来进行 token 的刷新:
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_ALLOW_REFRESH': True,
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}
在用户登录时,生成 token:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
re_dict = serializer.data
payload = jwt_payload_handler(user)
re_dict['token'] = jwt_encode_handler(payload)
re_dict['username'] = user.username
headers = self.get_success_headers(serializer.data)
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
在 urls
中注册路由:
urlpatterns = [
path('login/', obtain_jwt_token),
path('refresh/', refresh_jwt_token),
path('verify/', verify_jwt_token),
]