Day 28 JWT认证相关
一、JWT
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证,我们不再shiyongsession认证机制,而使用Json Web Token认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
那么我们现在回到JWT的主题上。
JWT的特点
- 体积小,因而传输速度快
- 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
- 严格的结构化。他自身在(payload中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期的信息,服务器无需再去连接数据库验证信息的有效性,并且payload支持为你的应用而定制化。
- 支持跨域验证,可以应用于单点登录。
JWT的构成
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。就像这样:
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Header
第一部分我们称它为头部(header),第二部分我们称其为荷载(payload,承载的数据),第三部分是签证(signature)。
完整的头部就像下面这样的JSON
{
'typ': 'JWT',
'alg': 'BASE64'
}
然后将头部进行base64加密(加密是对称的,可加可解),构成了第一部分
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==
Playload
这里是存放有效信息的地方,有效信息包含三个部分
-
标准中的注册声明(建议但不强制使用)
- iss:jwt签发者
- sub:jwt所面向的用户
- aud:接收jwt的一方
- exp:jwt的过期时间,这个过期时间必须大于签发时间
- nbf:定义在什么时间之前,该jwt都是不可用的
- iat:jwt的签发时间
- jti:jwt的唯一身份表示,主要用来作为一次性token,从而回避时序攻击。
-
公共的声明
这里可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加铭感信息,因为该部分在客户端可解密
-
私有的声明
是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归为明文信息。
定义一个payload:
{
"sub": "1234567890",
"name": "尼古拉斯赵四",
"admin": false
}
然后将其进行base64加密,得到jwt的第二部分
ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9
Signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
- header(base64加密后的)
- payload(base64加密后的)
- secret(加盐)
这个部分需要base64加密后的header和base64加密后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt:
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。
文档网站:http://getblimp.github.io/django-rest-framework-jwt/
二、 本质原理
JWT认证算法:签发与校验
- jwt分三段式:头.体.签名 (head.payload.sgin)
- 头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
- 头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
- 头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
- 体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
- 签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
- 用基本信息存储json字典,采用base64算法加密得到 头字符串
- 用关键信息存储json字典,采用base64算法加密得到 体字符串
- 用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
校验:根据客户端带token的请求 反解出 user 对象
- 将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
- 第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
- 再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
三、DRF项目的JWT认证开发流程(重点)
- 用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
- 校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注意:登录接口需要做 认证 + 权限 两个局部禁用
四、base64编码解码
import base64 # 导入模块
import json # 序列化
dic = {
"key": "what a beautiful day!",
"value": "but I still have to bring an umbrella"
}
# 序列化并指定编码格式
byte_dic = json.dumps(dic).encode('utf-8')
# 编码
dic_basesf = base64.b64encode(byte_dic)
print(dic_basesf)
# # b'eyJrZXkiOiAid2hhdCBhIGJlYXV0aWZ1bCBkYXlcdWZmMDEiLCAidmFsdWUiOiAiYnV0IEkgc3RpbGwgaGF2ZSB0byBicmluZyBhbiB1bWJyZWxsYSJ9'
# 解码
dic_str = base64.b64decode(dic_basesf)
print(dic_str)
# b'{"key": "what a beautiful day\\uff01", "value": "but I still have to bring an umbrella"}'
五、DRF-jwt安装和简单使用
1、安装
pip install djangorestframework-jwt
2、使用
默认使用auth的user表中创建的用户
# urls.py
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^login/', obtain_jwt_token),
]
postman测试
向后端接口发送post请求,携带用户名密码,即可看到生成的token,携带用户名密码,登陆成功返回token
3、obtain_jwt_token本质也是一个视图类,继承了APIView
-通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
-如果校验失败,返回错误信息
4、用户登录才能访问某个接口
我们先写了一个视图并配置好了路由
# urls.py
url(r'^text/(?P<pk>\d+)', views.TextAPI.as_view()),
# views.py
class TextAPI(GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookSerializer
def get(self, request, *args, **kwargs):
book = self.get_object()
ser = self.get_serializer(book)
return utils.APIResponse(data=ser.data)
这个时候未登录 没有token也是可以访问的,我们要想加个验证就需要使用jwt
内部的认证类,拿过来局部配置就可以了
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
,但是只配置他是不行的,是没有用的,我们仍然可以访问到具体数据,我们还需要搭配一个权限类来使用from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class TextAPI(GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookSerializer
# 只配置他是不行的,不管是否登录,都能访问,他需要搭配一个权限类
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated, ]
def get(self, request, *args, **kwargs):
book = self.get_object()
ser = self.get_serializer(book)
return utils.APIResponse(data=ser.data)
这是最简单的使用,注意
下面图片中Vaule一定要带 jwt + 一个空格 +token
5、如果用户携带了token,并且配置了JSONWebTokenAuthentication
,从request.user就能拿到当前登录用户,如果没有携带,当前登录用户就是匿名用户
注意:如果不加DRF内置的IsAuthenticated
那他登录也可以访问,没登录也可以访问
6、obtain_jwt_token源码分析
六、控制登录接口返回的数据格式
1 控制登录接口返回的数据格式如下
{
code:100
msg:登录成功
token:asdfasfd
username:egon
}
2 写一个函数
from homework.serializer import UserReadOnlyModelSerializer
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': '登录成功',
'token': token,
'user': UserReadOnlyModelSerializer(instance=user).data
}
3 在setting.py中配置
import datetime
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler',
}
七、自定义基于jwt的认证类
1 自己实现基于jwt的认证类,通过认证,才能继续访问,通不过认证就返回错误
2 代码如下
class JwtAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
# 认证逻辑()
# token信息可以放在请求头中,请求地址中
# key值可以随意叫
# token=request.GET.get('token')
token=request.META.get('HTTP_Authorization'.upper())
# 校验token是否合法
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('过期了')
except jwt.DecodeError:
raise AuthenticationFailed('解码错误')
except jwt.InvalidTokenError:
raise AuthenticationFailed('不合法的token')
user=self.authenticate_credentials(payload)
return (user, token)
3 在视图类中配置
authentication_classes = [JwtAuthentication, ]
Another
集群:物理形态
同一个业务,部署在多个服务器上
分布式:工作方式
一个业务分拆多个子业务,部署在不同的服务器上
小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群
反向代理
是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
正向代理
它隐藏了真实的请求客户端, 服务端不知道真实的客户端是谁, 客户端清求的服务都被代理服务器代替来请求。
对称加密
指的就是加、解密使用的同是一串密钥,所以被称做对称加密。对称加密只有一个密钥作为私钥。常见的对称加密算法:DES,AES等。
优缺点
对称加密相比非对称加密算法来说,加解密的效率要高得多、加密速度快。但是缺陷在于对于密钥的管理和分发上比较困难,不是非常安全,密钥管理负担很重。
非对称加密
指的是加、解密使用不同的密钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。
优缺点
安全性更高,公钥是公开的,密钥是自己保存的,不需要将私钥给别人。缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
来源:oschina
链接:https://my.oschina.net/u/4380905/blog/4728547