前言
这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题
于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。
微信小程序搜索:Python面试宝典
或可关注原创个人博客:https://lienze.tech
也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习
JWT
- 什么是jwt?
无状态下可以通过凭证发给客户端,客户端请求时持有,保持一些状态
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token认证机制
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).
该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密
- session认证
我们知道,http协议本身是一种无状态的协议
而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,根据http协议,我们并不能知道是哪个用户发出的请求,为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了
这就是传统的基于session认证
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来
- session
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存或服务端中,而随着认证用户的增多,服务端的开销会明显增大
- token
基于token的鉴权机制类似于http协议也是无状态的
它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利
流程上是这样的
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
jwt的设置分为三部分:头部、载荷、以及签证
jwt-header
:base64编码
jwt
的头部固定为两个值部分typ
和alg
这里的头部数据就是一个json串,两个字段是必须的,不能多也不能少
alg
字段指定了生成C的算法,默认值是HS256
typ
字段指明json串类型为jwt
{
"typ": "JWT",
"alg": "HS256"
}
jwt-payload
:base64编码
是一个json数据,是表明用户身份的数据,可自行指定字段很灵活,也有固定字段表示特定含义
以下是固定字段的含义
字段 | 解释 |
---|---|
iss | jwt的签发者 |
iat | jwt-token创建时间,unix时间戳格式 |
exp | jwt-token过期时间,要大于签发时间 |
aud | 接收jwt-token的一方 |
sub | jwt-token所面向的用户 |
nbf | 定义在什么时间之前,该jwt都是不可用的 |
jti | token的唯一标识,主要 |
jwt-signature
将前两部分使用HS256加密并且进行base64
编码之后
使用服务端生成的密钥加密得到signature
之后在客户端发送jwt到服务端时,通过密钥解密signature,校验第二与第一部分是否相符
用户登陆分发jwt
- 接口定义
POST:username、password
URL:authorizations/
返回值:username、user_id、token
在用户登陆成功之后,需要向用户返回jwt,在之后需要验证用户身份信息时,再进行用户身份的核验
这部分流程可以使用Django REST Framework JWT
来完成
pip install djangorestframework-jwt
http://getblimp.github.io/django-rest-framework-jwt/
# 文档
- 配置settings
#settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
- 配置jwt过期时间
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
#'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=120),
}
- 手动创建令牌
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# 创建payload的函数
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 创建token的函数
payload = jwt_payload_handler(user) # 创建载荷
token = jwt_encode_handler(payload) # 创建token
- 巧的是,Django REST framework JWT中恰恰提供了用户验证登录功能
只需要我们在路由中配置以下接口即可
#urls.py
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/',obtain_jwt_token),
]
- 但是这个接口仅仅只是返回了jwt-token值而已,我们还需要返回用户ID和用户名,需要重载源码中所使用到的
jwt_response_payload_handler
函数
#settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'users.views.jwt_response_payload_handler',
# 重定义jwt_response_payload_handler函数并覆盖默认属性
# token有效期配置
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
还要重载返回jwt-token的函数
#views.py
#views.py
def jwt_response_payload_handler(token, user, request):
return {
'token': token,
'username': user.username,
'user_id': user.id,
}
后端校验JWT函数
- 后端校验
JWT
函数,同样也有一个现成的接口可以使用
将令牌传递给验证端点将返回200响应和令牌(如果有效)
否则,它将返回400 Bad Request
以及识别令牌无效的错误
#urls.py
from rest_framework_jwt.views import verify_jwt_token
path('verify/',verify_jwt_token),
前端操作存储空间
sessionStorage.变量名 = 变量值 // 保存数据
sessionStorage.变量名 // 读取数据
sessionStorage.clear() // 清除所有sessionStorage保存的数据
localStorage.变量名 = 变量值 // 保存数据
localStorage.变量名 // 读取数据
localStorage.clear() // 清除所有localStorage保存的数据
前端通过axios异步提交登陆数据,
增加用户验证机制
jWT扩展的登录视图,在收到用户名与密码时
通过调用Django的认证系统中提供的**authenticate()**来检查用户与密码是否正确。
我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。
修改Djnago认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend, 并重写authenticate方法
authenticate是一个方法,验证账号密码是否正确,需要提供username和password2个参数.,验证成功返回一个user对象,失败则返回None
- 先来看看原生的验证函数
authenticate(self, request, username=None, password=None, **kwargs)
# request 本次认证的请求对象
# username 本次认证提供的用户账号
# password 本次认证提供的密码
想要让用户既可以以用户名登录,也可以以手机号登录
那么对于authenticate方法而言,username参数即表示用户名或者手机号
接下来重载authenticate方法
#views.py
# 根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
# 若查找到User对象,调用User对象的check_password方法检查密码是否正确
class AuthorBackend(ModelBackend):
def authenticate(self,request, username=None, password=None, **kwargs):
try:
user = models.User.objects.get(phone=username)
except:
user = None
else:
try:
user = models.User.objects.get(username=username)
except:
user = None
else:
try:
user = models.User.objects.get(email=username)
except:
user = None
if user and user.check_password(password):
return user
- 将自定义用户验证器设置到django的settings文件下
#settings.py
AUTHENTICATION_BACKENDS = [
'users.views.AuthorBackend',
]
总结
-
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用
-
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
-
它不需要在服务端保存会话信息, 所以它易于应用的扩展
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- 保护好secret私钥,该私钥非常重要。
- 如果可以,请使用https协议