【Python使用】嘿马头条完整开发md笔记第6篇:Git工用流,Json Web Token(JWT)【附代码文档】
嘿马头条项目从到完整开发笔记总结完整教程(附代码资料)主要内容讲述:课程简介,ToutiaoWeb虚拟机使用说明1 产品介绍,2 原型图与UI图,3 技术架构,4 开发,1 需求,2 注意事项。数据库,理解ORM1 简介,2 安装,3 数据库连接设置,4 模型类字段与选项,5 构建模型类映射。数据库,SQLAlchemy操作1 新增,2 查询,3 更新,4 删除,5 事务,1. 复制集与分布式。数据库,分布式ID1 方案选择,2 头条,1 理解索引,2 SQL查询优化,3 数据库优化。数据库,Redis1 Redis事务,2 Redis持久化,3 Redis高可用,4 Redis集群,5 用途,6 相关补充阅读。Git工用流,调试方法。OSS对象存储,七牛云存储。缓存,缓存架构缓存数据的类型,缓存数据的保存方式,有效期 TTL (Time to live),缓存淘汰 eviction。缓存,缓存问题1 缓存穿透,2 缓存雪崩,缓存设计,持久存储设计。APScheduler定时任务,定时修正统计数据1. 什么是RPC,2. 背景与用途,3. 概念说明,4. 优缺点,架构,使用方法。RPC,编写客户端。即时通讯,Socket.IO1 简介,2 Python服务器端开发,3 Python客户端。Elasticsearch,简介与原理概念,Elasticsearch 集群(cluster),索引,类型和映射。Elasticsearch,文档。单元测试,部署相关数据库性能,缓存雪崩,缓存编写。缓存模式缓存的架构,缓存数据,缓存数据的有效期和淘汰策略,淘汰策略,头条项目缓存数据的设计。
全套笔记资料代码移步: 前往gitee仓库查看
感兴趣的小伙伴可以自取哦,欢迎大家点赞转发~
全套教程部分目录:
部分文件图片:
Git工用流
调试方法
-
判断问题发生在前端还是后端
-
如果前端为网页,可以通过网页调试工具里面的network判断
- 在前端中触发一次接口调用的请求
- 查看network中对应的请求记录
- 如果没有发现有新的请求记录,表示前端有问题,并未发起请求
- 如果有请求记录,返回的状态码为200,但是页面没有想要的效果,表示前端没有正确处理200的响应
- 如果返回的状态码为4xx, 表示前端构造的请求有问题,比如404没有正确的请求网址,401、403请求的身份有问题,405请求的方式有问题,400表示构造的请求参数不正确(类型不对或缺少参数)
- 如果返回500状态码,表示服务器端出现问题
-
如果前端不是网页,比如app,通过日志的访问请求记录判断
在服务器中查看日志文件的方法:
tail flask.log # 查看最后一条记录
tail -n 100 flask.log # 查看最新的100条记录
tail -f flask.log # 实时查看
-
如果是后端出现的问题,通过pycharm或日志来判断
-
查看记录错误的日志,根据日志的信息判断错误
JWT认证方案
JWT & JWS & JWE
Json Web Token(JWT)
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在两个组织之间传递安全可靠的信息。
官方定义:JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties
现在网上大多数介绍JWT的文章实际介绍的都是JWS(JSON Web Signature),也往往导致了人们对于JWT的误解,但是JWT并不等于JWS,JWS只是JWT的一种实现,除了JWS外,JWE(JSON Web Encryption)也是JWT的一种实现。
下面就来详细介绍一下JWT与JWE的两种实现方式:
JSON Web Signature(JWS)
JSON Web Signature是一个有着简单的统一表达形式的字符串:
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。 JSON内容要经Base64 编码生成字符串成为Header。
载荷(PayLoad)
payload的五个字段都是由JWT的标准所定义的。
- iss: 该JWT的签发者
- sub: 该JWT所面向的用户
- aud: 接收该JWT的一方
- exp(expires): 什么时候过期,这里是一个Unix时间戳
- iat(issued at): 在什么时候签发的
后面的信息可以按需补充。 JSON内容要经Base64 编码生成字符串成为PayLoad。
签名(signature)
这个部分header与payload通过header中声明的加密方式,使用密钥secret进行加密,生成签名。 JWS的主要目的是保证了数据在传输过程中不被修改,验证数据的完整性。但由于仅采用Base64对消息内容编码,因此不保证数据的不可泄露性。所以不适合用于传输敏感数据。
JSON Web Encryption(JWE)
相对于JWS,JWE则同时保证了安全性与数据完整性。 JWE由五部分组成:
JWE组成
具体生成步骤为:
- JOSE含义与JWS头部相同。
- 生成一个随机的Content Encryption Key (CEK)。
- 使用RSAES-OAEP 加密算法,用公钥加密CEK,生成JWE Encrypted Key。
- 生成JWE初始化向量。
- 使用AES GCM加密算法对明文部分进行加密生成密文Ciphertext,算法会随之生成一个128位的认证标记Authentication Tag。 6.对五个部分分别进行base64编码。
可见,JWE的计算过程相对繁琐,不够轻量级,因此适合与数据传输而非token认证,但该协议也足够安全可靠,用简短字符串描述了传输内容,兼顾数据的安全性与完整性。
JWT的Python库
独立的JWT Python库
-
itsdangerous
-
JSONWebSignatureSerializer
-
TimedJSONWebSignatureSerializer (可设置有效期)
-
pyjwt
[
安装
$ pip install pyjwt
用例
>>> import jwt
>>> encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
>>> encoded_jwt
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
{'some': 'payload'}
头条项目封装
import jwt
from flask import current_app
def generate_jwt(payload, expiry, secret=None):
"""
生成jwt
:param payload: dict 载荷
:param expiry: datetime 有效期
:param secret: 密钥
:return: jwt
"""
_payload = {'exp': expiry}
_payload.update(payload)
if not secret:
secret = current_app.config['JWT_SECRET']
token = jwt.encode(_payload, secret, algorithm='HS256')
return token.decode()
def verify_jwt(token, secret=None):
"""
检验jwt
:param token: jwt
:param secret: 密钥
:return: dict: payload
"""
if not secret:
secret = current_app.config['JWT_SECRET']
try:
payload = jwt.decode(token, secret, algorithm=['HS256'])
except jwt.PyJWTError:
payload = None
return payload
头条项目实施方案
需求
设置有效期,但有效期不宜过长,需要刷新。
如何解决刷新问题?
-
手机号+验证码(或帐号+密码)验证后颁发接口调用token与refresh_token(刷新token)
-
Token 有效期为2小时,在调用接口时携带,每2小时刷新一次
-
提供refresh_token,refresh_token 有效期14天
-
在接口调用token过期后凭借refresh_token 获取新token
-
未携带token 、错误的token或接口调用token过期,返回401状态码
-
refresh_token 过期返回403状态码,前端在使用refresh_token请求新token时遇到403状态码则进入用户登录界面从新认证。
-
token的携带方式是在请求头中使用如下格式:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
注意:Bearer前缀与token中间有一个空格
实现
注册或登录获取token
toutiao/resources/user/passport.pty
class AuthorizationResource(Resource):
"""
认证
"""
def _generate_tokens(self, user_id, with_refresh_token=True):
"""
生成token 和refresh_token
:param user_id: 用户id
:return: token, refresh_token
"""
# 颁发JWT
now = datetime.utcnow()
expiry = now + timedelta(hours=current_app.config['JWT_EXPIRY_HOURS'])
token = generate_jwt({'user_id': user_id, 'refresh': False}, expiry)
refresh_token = None
if with_refresh_token:
refresh_expiry = now + timedelta(days=current_app.config['JWT_REFRESH_DAYS'])
refresh_token = generate_jwt({'user_id': user_id, 'refresh': True}, refresh_expiry)
return token, refresh_token
def post(self):
"""
登录创建token
"""
json_parser = RequestParser()
json_parser.add_argument('mobile', type=parser.mobile, required=True, location='json')
json_parser.add_argument('code', type=parser.regex(r'^\d{6}$'), required=True, location='json')
args = json_parser.parse_args()
mobile = args.mobile
code = args.code
# 从redis中获取验证码
key = 'app:code:{}'.format(mobile)
try:
real_code = current_app.redis_master.get(key)
except ConnectionError as e:
current_app.logger.error(e)
real_code = current_app.redis_slave.get(key)
try:
current_app.redis_master.delete(key)
except ConnectionError as e:
current_app.logger.error(e)
if not real_code or real_code.decode() != code:
return {'message': 'Invalid code.'}, 400
# 查询或保存用户
user = User.query.filter_by(mobile=mobile).first()
if user is None:
# 用户不存在,注册用户
user_id = current_app.id_worker.get_id()
user = User(id=user_id, mobile=mobile, name=mobile, last_login=datetime.now())
db.session.add(user)
profile = UserProfile(id=user.id)
db.session.add(profile)
db.session.commit()
else:
if user.status == User.STATUS.DISABLE:
return {'message': 'Invalid user.'}, 403
token, refresh_token = self._generate_tokens(user.id)
return {'token': token, 'refresh_token': refresh_token}, 201
请求钩子
common/utils/middlewares.py
from flask import request, g
from .jwt_util import verify_jwt
def jwt_authentication():
"""
根据jwt验证用户身份
"""
g.user_id = None
g.is_refresh_token = False
authorization = request.headers.get('Authorization')
if authorization and authorization.startswith('Bearer '):
token = authorization.strip()[7:]
payload = verify_jwt(token)
if payload:
g.user_id = payload.get('user_id')
g.is_refresh_token = payload.get('refresh')
强制登录装饰器
common/utils/decorators.py
def login_required(func):
"""
用户必须登录装饰器
使用方法:放在method_decorators中
"""
@wraps(func)
def wrapper(*args, **kwargs):
if not g.user_id:
return {'message': 'User must be authorized.'}, 401
elif g.is_refresh_token:
return {'message': 'Do not use refresh token.'}, 403
else:
return func(*args, **kwargs)
return wrapper
更新token接口
toutiao/resources/user/passport.py ```python class AuthorizationResource(Resource): """ 认证 """
...
# 补充put方式 更新token接口
def put(self):
"""
刷新token
"""
user_id = g.user_id
if user_id and g.is_refresh_token:
token, refresh_token = self._generate_tokens(user_id, with_refresh_token=False)
return {'token': tok
- 点赞
- 收藏
- 关注作者
评论(0)