换句话来说,为什么需要session、cookie和token
这个认证信息需要的原因,就是比如说我们今天使用淘宝点击添加购物车,然后就需要一个请求,但是发送这个请求之前,需要验证是哪个用户所登录,所以就需要一个类似通行码的东西去标识已登录。所以就存在了这些东西是吧?
这个session的话,首先出现了他,他存储再服务端,但是服务器压力就很大,如果出现了损坏,大家都得重新登录,所以出现了cookie。cookie是存储在客户端的,但是如果web服务器做了负载均衡,下一个操作请求到了另一个服务器session就会丢失。就是无法跨设备免密登录。然后就出现了token,token是随着登录成功一起生成保存在客户端,客户端每次访问的话都是携带者token去访问服务端,所以就更加好
当我们登录成功以后,并没有一个相应的标识,做出特殊的处理。这里,我们登录成功以后,就需要一个通行证的东西,去标识我们已经登录。这里可以是session、token标识。本次,我们将使用jwt(JSON WEB TOKEN)
jwt是用于生成token
jwt官网:jwt.io
jwt使用方法:github
pip install pyjwt
# forum/handler/UserHandler.py
from forum.wtforms import UserForm,LoginUserForm
from forum import manager
from forum.models import UserModel
from uuid import uuid4
from forum.handler.BaseHandler import BaseHandler
from forum.utils.email_utils import send_mail
from forum.utils.redis_utils import *
from random import randint
import jwt
from config import secret,email
# 登录接口
class LoginHandler(BaseHandler):
async def post(self):
# 记录登录信息
print("1234")
rs_data = {}
# 获取表单数据
user_form = LoginUserForm(self.request.arguments)
# 验证是否符合form表单输入的规则
if user_form.validate():
try:
# 数据库中获取
user = await manager.get(UserModel, email=user_form.email.data, password=user_form.password.data)
# 获取到了,登录成功
print(user_form.email.data)
print(user_form.password.data)
rs_data['code'] =200
rs_data['msg'] = '登录成功!!!'
payload = {
'email': user_form.email.data
}
# 生成一个用户信息(加密)token,返回给前端,下一次访问时,携带用户信息回来即可
token = jwt.encode(payload, secret, algorithm='HS256')# 参数含义:加密谁,加密的密码(盐)、加密的算法
rs_data['token'] = token
except Exception as e:
# 获取不到,登录失败
rs_data['code'] = 401
rs_data['msg'] = '用户名或密码错误'
else:
rs_data['code'] = 401
rs_data['msg'] = '用户名不符合规范'
for f in user_form.errors:
print(user_form.errors)
rs_data[f] = user_form.errors[f][0]
self.finish(rs_data)
当我们登录完成以后,前端并没有展示出登录成功的状态。这里我们需要一个接口,获取用户登录成功的信息,获取他的登录通行证token。从token中解析是否存在数据email,如果存在则登录成功。(token是随着登录时一起产生在服务端的,存放在请求头中)
# forum/models.py
# 用于创建数据表模型
from peewee import *
from forum import database
from datetime import datetime
# 创建基类:减少重复代码
class BaseModel(Model):
create_time = DateTimeField(default = datetime.now)
# 创建函数,用于返回用户的信息位字典形式
def to_json(self) -> dict:
r = {}
for k in self.__data__.keys():
# 判断数据是否是create_time(时间不是字符串)
if k == 'create_time':
r[k] = str(getattr(self,k))
else:
r[k] = getattr(self,k)
return r
# forum/handler/UserHandler.py
from forum.wtforms import UserForm,LoginUserForm
from forum import manager
from forum.models import UserModel
from uuid import uuid4
from forum.handler.BaseHandler import BaseHandler
from forum.utils.email_utils import send_mail
from forum.utils.redis_utils import *
from random import randint
import jwt
from config import secret,email
# token获取用户信息接口实现——免密登录
class GetUserHandler(BaseHandler):
async def get(self):
rs_data = {}
# 获取token
token = self.request.headers.get('token')
# 从token值解析出email
payload = jwt.decode(token, secret, algorithms=['HS256'])
if payload:
# 通过email查询数据
email = payload.get('email')
user = await manager.get(UserModel, email=email)
# 判断是否存在这个用户
if user:
# 有数据,将用户信息返回给前端
rs_data['code'] = 200
rs_data['msg'] = '获取用户成功!!!'
rs_data['user'] = user.to_json()
# 没有数据,没有登录
else:
rs_data['code'] = 500
rs_data['msg'] = 'toekn错误!!!'
else:
rs_data['code'] = 500
rs_data['msg'] = '请登录后再操作!!!'
self.finish(rs_data)
装饰器
# forum/wtforms.py
# 完整注册(给t_user表)增加数据之前的数据验证
from wtforms_tornado import Form
from wtforms.fields import StringField
from wtforms.fields.simple import HiddenField
from wtforms.validators import DataRequired, Length
# 对t_user的数据验证
class UserForm(Form):
id = HiddenField()
email = StringField('账号', validators=[DataRequired(message='请填写合法的邮箱地址'),Length(min=5, max=20, message='请输入5-20长度的邮箱')])
nick_name = StringField('昵称', validators=[Length(min=2, max=10, message='请输入2-10长度的昵称')])
password = StringField('密码', validators=[Length(min=2, message='请输入2以上长度的密码')])
signature = StringField('签名')
pic = StringField('头像')
# 对登录的数据验证
class LoginUserForm(Form):
id = HiddenField()
email = StringField('账号', validators=[DataRequired(message='请填写合法的邮箱地址'),Length(min=5, max=20, message='请输入5-20长度的邮箱')])
nick_name = StringField('昵称')
password = StringField('密码', validators=[Length(min=2, message='请输入2以上长度的密码')])
signature = StringField('签名')
pic = StringField('头像')
优化免密登录
# forum/handler/UserHandler.py
from uuid import uuid4
from random import randint
import jwt
from forum.wtforms import UserForm,LoginUserForm
from forum import manager
from forum.models import UserModel
from forum.handler.BaseHandler import BaseHandler
from forum.utils.email_utils import send_mail
from forum.utils.redis_utils import *
from config import secret,email
from forum.decorators import login_required_async
# 调用wtforms做添加用户数据之前的数据表单验证
class AddUserHandler(BaseHandler):
async def post(self):
# 创建应该响应对象
rs_data = {}
# 接受请求的参数并封装到Form对象中
user_form = UserForm(self.request.arguments)
# 前端传递的验证码
code = self.get_body_argument('code')
# 后端的正确的验证码
db_code = get_code(user_form.email.data)
if code == db_code:
if user_form.validate():
# 从表单中获取email信息
email = user_form.email.data
try:
# 异步查询数据表信息
exist_user = await manager.get(UserModel,email = email)
if exist_user:
# 存在此用户,验证失败
rs_data['code'] = 500
rs_data['msg'] = '用户名已存在'
except Exception as e:
# 验证成功
# 异步保存数据到数据库中
user_form.id.data = uuid4()
await manager.create(UserModel,**user_form.data)
rs_data['code'] = 200
rs_data['msg'] = '注册成功'
else:
# 验证失败
rs_data['code'] = 500
rs_data['msg'] = '注册失败'
# 将验证失败的具体原因返回给rs_data,提示信息
for f in user_form.errors:
rs_data[f] = user_form.errors[f][0]
else:
# 验证失败
rs_data['code'] = 500
rs_data['msg'] = '验证码错误'
self.finish(rs_data)
# 发送邮件
class SendEmailHandler(BaseHandler):
# 用于给定4位随机验证码
def generate_code(self) -> int:
return randint(1000,9999)
# 发送邮件的接口
def post(self):
user_email = self.get_body_argument('email') # 获取前端form输入框中输入的邮箱
code = self.generate_code()
msg = f'您好,您正在使用{user_email}注册用户注册账号,您的验证码位 {code},如果不是本人操作,请忽略'
send_mail(email.get('uname'),email.get('pwd'),user_email,'用户注册' ,msg)
save_code(user_email,code)
# 登录接口
class LoginHandler(BaseHandler):
async def post(self):
# 记录登录信息
print("1234")
rs_data = {}
# 获取表单数据
user_form = LoginUserForm(self.request.arguments)
# 验证是否符合form表单输入的规则
if user_form.validate():
try:
# 数据库中获取
user = await manager.get(UserModel, email=user_form.email.data, password=user_form.password.data)
# 获取到了,登录成功
print(user_form.email.data)
print(user_form.password.data)
rs_data['code'] =200
rs_data['msg'] = '登录成功!!!'
payload = {
'email': user_form.email.data
}
# 生成一个用户信息(加密)token,返回给前端,下一次访问时,携带用户信息回来即可
token = jwt.encode(payload, secret, algorithm='HS256')# 参数含义:加密谁,加密的密码(盐)、加密的算法
rs_data['token'] = token
except Exception as e:
# 获取不到,登录失败
rs_data['code'] = 401
rs_data['msg'] = '用户名或密码错误'
else:
rs_data['code'] = 401
rs_data['msg'] = '用户名不符合规范'
for f in user_form.errors:
print(user_form.errors)
rs_data[f] = user_form.errors[f][0]
self.finish(rs_data)
# token获取用户信息接口实现——免密登录
class GetUserHandler(BaseHandler):
@login_required_async
async def get(self):
rs_data = {}
id = self._user_id
try:
user = await manager.get(UserModel,id=id)
# 判断是否存在这个用户
if user:
# 有数据,将用户信息返回给前端
rs_data['code'] = 200
rs_data['msg'] = '获取用户成功!!!'
rs_data['user'] = user.to_json()
except Exception as e:
rs_data['code'] = 500
rs_data['msg'] = '请登录后再操作!!!'
self.finish(rs_data)