JWT、base64编码和解码、drf-jwt快速使用、drf-jwt修改返回格式、自定义user表,签发token
JWT原理介绍
Json web token(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(sso)场景,JWT的声明一般被用来在身份提供者和服务者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其他业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密
token的应用于web方向的称之为jwt
JWT的构成
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature)。
header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload
荷载就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
jwt使用流程最核心的是:
签发:登录接口签发
认证:认证类认证
base64编码和解码
base64 可以把字符串编码成base64的编码格式:(大小写字母,数字和 =)eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9
base64可以把base64编码的字符串,解码回原来的格式
应用场景
jwt中使用
网络中传输字符串就可以使用base64编码
网络中传输图片,也可能使用base64的编码
base64编码和解码的过程
base64编码
import json
import base64
d = {'name': 'zxr', 'userid': 1, 'age': 18}
info = json.dumps(d)
# print(info) # {"name": "zxr", "userid": 1, "age": 18}
# 把字符串转成bytes格式的俩种方式
# 第一种:info.encode('utf-8')
# res = base64.b64encode(info.encode('utf-8'))
# print(res) # b'eyJuYW1lIjogInp4ciIsICJ1c2VyaWQiOiAxLCAiYWdlIjogMTh9'
# 第二种:强转
print(bytes(info, encoding='utf-8')) # b'{"name": "zxr", "userid": 1, "age": 18}'
base64解码过程
import json
import base64
d = {'name': 'zxr', 'userid': 1, 'age': 18}
info = json.dumps(d)
b'eyJuYW1lIjogInp4ciIsICJ1c2VyaWQiOiAxLCAiYWdlIjogMTh9'
res = base64.b64decode('eyJuYW1lIjogInp4ciIsICJ1c2VyaWQiOiAxLCAiYWdlIjogMTh9')
print(res) # b'{"name": "zxr", "userid": 1, "age": 18}'
将图片保存起来
import json
import base64
d = {'name': 'lqz', 'userid': 6, 'age': 19}
info = json.dumps(d)
print(info)
# 把字符串使用base64编码
res=base64.b64encode(info.encode('utf-8'))
print(res) # eyJuYW1lIjogImxxeiIsICJ1c2VyaWQiOiA2LCAiYWdlIjogMTl9
res=base64.b64decode(s)
with open('code.png','wb') as f:
f.write(res)
drf-JWT快速使用
jwt:签发(登录接口) 认证(认证类)
django中使用jwt
# 可以自己写
https://github.com/jpadilla/django-rest-framework-jwt (比较老)
https://github.com/jazzband/djangorestframework-simplejwt (比较新)
安装
pip3 install djangorestframework-jwt
快速使用
1)迁移表,因为它默认使用auth的user表签发token
2)创建超级用户(auth的user表中要右记录)
3)不需要写登录接口了,如果是使用auth的user表作为用户表,它可以快速签发
4)签发(登录):只需要在路由中配置(因为它帮咱们写好登录接口了)
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
# 返回结果
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Inp4ciIsImV4cCI6MTY2NTU3MjU4OSwiZW1haWwiOiJ6eHJAMTYzLmNvbSJ9.0jDd56Jk04-SdZ4AchLHkcfLECS1RvhFwAQ8VoNKiMM"
}
5)认证(认证类):导入,配置在视图类上
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class TestView(APIView):
authentication_classes = [JSONWebTokenAuthentication, ]
# 是否登录的权限类
permission_classes = [IsAuthenticated, ]
def get(self, request):
return Response('ok')
6)前端访问时,token需要放在请求头中
Authorization:jwt token串
# 例如:
Authorization:jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Inp4ciIsImV4cCI6MTY2NTU3MzMzMCwiZW1haWwiOiJ6eHJAMTYzLmNvbSJ9.DAX1wLhHYnpGwVzGvMnSFzUudkPgXsYvlrfk-XFdSAc
drf-jwt修改返回格式
登录成功后,前端看到的格式,太固定了,只有token,我们想做成:
{code:100,msg:'登录成功',token:adfasdfasdf}
固定写法:
写一个函数,函数返回什么,前端就看到什么,配置在配置文件中
使用步骤:
写一个函数
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': '登录成功',
'username': user.username,
'token': token
}
将函数配置在配置文件中
# djangorestframework-jwt的配置,这个配置了以后优先使用这个
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
}
以后登录接口返回的格式就是咱们写的函数的返回值,前提是auth_user表才可以这么做
自定义user表,签发token
view.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from app01.models import Userinfo
from rest_framework.exceptions import APIException
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 自己基于自定义的用户表签发token
class UserView(APIView):
def post(self, request):
try:
username = request.data.get('username')
password = request.data.get('password')
user = Userinfo.objects.get(username=username, password=password)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'token': token})
except Exception as e:
raise APIException('用户表名或密码错误')
urls.py
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from app01 import views
urlpatterns = [
path('user/login/', views.UserView.as_view()),
]
models.py
from django.db import models
# Create your models here.
class Userinfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
自定义认证类,验证token
view.py
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# 放到头中:token-->HTTP_TOKEN Authorization--->HTTP_AUTHORIZATION
print(request.META)
jwt_value = request.META.get('HTTP_TOKEN')
# 验证token是否合法,jwt模块下一定有个验证token的函数
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
raise AuthenticationFailed('token过期了')
except jwt.DecodeError:
raise AuthenticationFailed('token解码失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('认证失败')
# 执行到这,说明token合法,payload可以使用
user_id = payload.get('user_id')
user = UserInfo.objects.filter(pk=user_id).first() # 每次都要查数据库,效率不太好
return (user, jwt_value)
# return (payload,jwt_value)
额外小知识
请求头中:X_FORWARDED_FOR 代指什么?
X-Forwarded-For 是一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址,但是这个IP却未必是真实的,我们后面会回来描述这个问题。一些开发者为了获取客户IP,我们经常会使用request.remote_ip来获得用户IP。但是很多用户都是通过代理来访问服务器的,如果使用remote_ip这个全局变量来获取IP,开发者往往获得的是代理服务器的IP,并不是用户真正的IP。
"客户端=>正向代理=>透明代理=>服务器反向代理=>Web服务器"
集群与分布式
集群:多个人在一起做同样的事,也就是说,同一个业务部署在多个服务器上
分布式:多个人在一起做不同的事,也就是说,一个业务拆分多个子业务部署在多个服务器上
集群主要的使用场景是为了分担请求的压力,但是,当压力增大的时候,可能需要存储的部分,比如mysql无法面对大量的写操作,因为MySQL做成集群后,主要的写压力还是在master(主机)的机器上,其他slave(从机)机器无法分担写压力,这时就引出了“分布式”!
分布式解决了中心化管理的问题。把所有的任务叠加到一个节点处理,效率太慢,所以把一个大的问题拆分成多个小问题,并分别解决,最终协同合作。
分布式主要工作:分解任务,把任务拆解。
# 再深入理解一下集群和分布式及其区别:
分布式:把一个大业务拆分成多个子业务,每个子业务都是一套独立的系统,子业务之间相互协作最终完成整体的大业务。
集群:把处理同一个业务的系统部署多个节点 。
把一套系统拆分成不同的子系统部署在不同服务器上,这叫分布式。
把多个相同的系统部署在不同的服务器上,这叫集群。部署在不同服务器上的相同系统必然要做“负载均衡”。
集群主要是简单加机器解决问题,对于问题本身不做任何分解。
分布式处理里必然涉及任务分解与答案归并。分布式中的某个子任务节点,可以是一个集群,该集群中的任一节点都作为一个完整的任务出现。
集群和分布式都是由多个节点组成,但集群中各节点间基本不需要通信协调,而分布式中各个节点的通信协调是必不可少的。