Day 28 JWT认证相关
1、JWT
在用户注册或登陆后,咱们想记录用户的登陆状态,或者为用户建立身份认证的凭证,咱们再也不shiyongsession认证机制,而使用Json Web Token认证机制。前端
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明通常被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也能够增长一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。python
基于token的鉴权机制
基于token的鉴权机制相似于http协议也是无状态的,它不须要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不须要去考虑用户在哪一台服务器登陆了,这就为应用的扩展提供了便利。git
流程上是这样的:github
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器经过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必需要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,通常咱们在服务端这么作就能够了Access-Control-Allow-Origin: *
。web
那么咱们如今回到JWT的主题上。算法
JWT的特色
- 体积小,于是传输速度快
- 传输方式多样,能够经过URL/POST参数/HTTP头部等方式传输
- 严格的结构化。他自身在(payload中)就包含了全部与用户相关的验证消息,如用户可访问路由、访问有效期的信息,服务器无需再去链接数据库验证信息的有效性,而且payload支持为你的应用而定制化。
- 支持跨域验证,能够应用于单点登陆。
JWT的构成
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.
连接一块儿就构成了Jwt字符串。就像这样:数据库
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Header
第一部分咱们称它为头部(header),第二部分咱们称其为荷载(payload,承载的数据),第三部分是签证(signature)。django
完整的头部就像下面这样的JSONjson
{ '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/
2、 本质原理
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对象就是合法的登陆用户
3、DRF项目的JWT认证开发流程(重点)
- 用帐号密码访问登陆接口,登陆接口逻辑中调用 签发token 算法,获得token,返回给客户端,客户端本身存到cookies中
- 校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,全部视图类请求,都会进行认证校验,因此请求带了token,就会反解出user对象,在视图类中用request.user就能访问登陆的用户
注意:登陆接口须要作 认证 + 权限 两个局部禁用
4、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"}'
5、DRF-jwt安装和简单使用
一、安装
pip install djangorestframework-jwt
二、使用
默认使用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
三、obtain_jwt_token本质也是一个视图类,继承了APIView
-经过前端传入的用户名密码,校验用户,若是校验经过,生成token,返回 -若是校验失败,返回错误信息
四、用户登陆才能访问某个接口
咱们先写了一个视图并配置好了路由
# 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
五、若是用户携带了token,而且配置了JSONWebTokenAuthentication
,从request.user就能拿到当前登陆用户,若是没有携带,当前登陆用户就是匿名用户
注意:若是不加DRF内置的IsAuthenticated
那他登陆也能够访问,没登陆也能够访问
六、obtain_jwt_token源码分析
6、控制登陆接口返回的数据格式
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', }
7、自定义基于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等。
优缺点
对称加密相比非对称加密算法来讲,加解密的效率要高得多、加密速度快。可是缺陷在于对于密钥的管理和分发上比较困难,不是很是安全,密钥管理负担很重。
非对称加密
指的是加、解密使用不一样的密钥,一把做为公开的公钥,另外一把做为私钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。
优缺点
安全性更高,公钥是公开的,密钥是本身保存的,不须要将私钥给别人。缺点:加密和解密花费时间长、速度慢,只适合对少许数据进行加密。