使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
1.jwt的数据返回设置
重写jwt_response_payload_handler 可以实现 返回token和其他数据
登录成功后 查看调试工具中Resource 中 localstorage ,其中会存放token
''' utils/users.py '''
# user 就是我们认证之后的user
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token':token,
'username':user.username,
'user_id':user.id
}
''' settings.py'''
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# 设置jwt 返回数据的函数
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':'utils.user.jwt_response_payload_handler'
}
2.注册成功之后返回token
笔者出现过的bug:在create方法中没有给user对象添加token字段时,注册账号,之后在create方法中添加token字段后,用该账号登录时,其对象并没有token字段, 所以会请求不到token。
确认注册完成后 生成token
即在数据入库后 注册完成 生成token
点击注册完成后 触发的一系列事件
其中在create中 save入库后 即注册完成
生成token 的代码:
def create():
...
# 此处注意导包 很容易导入错误的包
from rest_framework_jwt.settings import api_settings
# 需要获取2个方法
jwt_payload_handler = api.settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api.settings.JWT_ENCODE_HANDLER
# 1.jwt_payload_handler需要将用户的信息传递给这个函数,函数自己会获取user中的数据
# 获取之后,会生成一个字符串
payload = jwt_payload_handler(user)
# 2.需要对payload 进行编码 编码之后的才是token
token = jwt_encode_handler(payload)
# 序列化的时候返回token
# 序列化器中有该字段 但模型类中没有这个字段
# 所以动态添加属性
user.token = token
return user
######针对上面 user.token = token 举的例子 #########
def Person(object):
name = ''
p = Person()
p.name = 'it'
print(p.name)
# 这个可以打印 动态添加属性 只对当前对象起作用
p.age = 20
print(p.age)
# 这个不能打印
p2 = Person()
print(p2.age)
''' serializer.py ''''
class RegisterCreateSerializer(serializers.ModelSerializer):
...
# 添加新字段
token = serializers.CharField(label='token',read_only=True)
# token 只是应用于 序列化(对象转换为JSON)操作的时候使用,所以只能读 需要添加read_only
# 不加read_only会报错 提示 token字段为必填项
# read_only 表示只在序列化的时候该字段起作用
class Meta:
model = Users
fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token') # 增加token
''' js/register.js '''
在前端文件中增加保存token
var vm = new Vue({
...
methods: {
on_submit: function(){
axios.post(...)
.then(response => {
// 记录用户的登录状态
sessionStorage.clear();
localStorage.clear();
localStorage.token = response.data.token;
localStorage.username = response.data.username;
localStorage.user_id = response.data.id;
location.href = '/index.html';
})
.catch(...)
}
}
})
3.多账号登录 认证
增加支持用户名与手机号均可作为登录账号
JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。
我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。
修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。
authenticate(self, request, username=None, password=None, **kwargs)方法的参数说明:
request 本次认证的请求独享
username 本次认证提供的用户账号
password 本次认证提供的密码
我们想要让用户既可以以用户名登录,也可以以手机号登录,那么对于authenticate方法而言,username参数即表示用户名或者手机号。
重写authenticate方法的思路:
根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
若查找到User对象,调用User对象的check_password方法检查密码是否正确
''' utils/users.py '''
def get_user_by_account(username):
# 根据用户输入的信息 判断用户输入的是手机号还是用户名
try:
if re.match('1[3-9]\d{9}',username):
# 用户名满足手机号的规则
user = Users.objects.get(mobile=username)
else:
user = Users.objects.get(username=username)
except Users.DoesNotExist:
user = None
return user
class UsernameMobileAuthModelBackend(ModelBackend):
def authenticate(self,request,username=None,password=None,**kwargs):
# 1. 先判断输入类型 再进行查询
user = get_user_by_account(username)
# 2. 校验
if user is not None and user.check_password(password):
return user
return None
class SettingsBackend(object):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None):
# 1. 根据用户在用户名的地方输入的内容进行判断,如果用户输入的是手机号,我们就根据手机号查询用户
# 否则就根据 用户名进行查询
user = get_user_by_account(username)
# 2如果用户查询出来,我们再校验密码
if user is not None and user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
return Users.objects.get(pk=user_id)
except Users.DoesNotExist:
return None
需要改变认证后端的类
在配置文件中告知Django使用我们自定义的认证后端
'''settings.py '''
# 改变认证后端的类
AUTHENTICATION_BACKENDS = [
# 'utils.users.UsernameMobileAuthModelBackend',
'utils.users.SettingsBackend'
]
# 不加 这个 会报
# {
# "non_field_errors": [
# "无法使用提供的认证信息登录。"
# ]
# }
Django REST framework JWT提供了登录获取token的视图,可以直接使用,在users应用中的urls添加路由信息
''' urls.py '''
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
#添加登录认证
url(r'^auths/',obtain_jwt_token),
]