POST /users/login 用户登录
请求体 application/json
{
"password":"string",
"email":"string"
}
响应
200 登录成功
400 用户名密码错误
{
"password":"abc",
"email":"[email protected]"
}
# 修改user/urls.py文件
from django.conf.urls import re_path
from .views import reg,login
urlpatterns = [
re_path(r'^$',reg), #/users/
re_path(r'^login$',login),
]
# user/views.py文件中增加如下代码
import jwt
import datetime
import bcrypt
from django.conf import settings
# 筛选所需要的字段
def jsonify(instance,allow=None,exclude=[]):
# allow优先,如果有,就使用allow指定的字段,这时候exclude无效
# allow如果为空,就全体,但要看看有exclude中的要排除
modelcls = type(instance)
if allow:
fn = (lambda x:x.name in allow)
else:
fn = (lambda x:x.name not in exclude)
# from django.db.models.options import Options
# m:Options = modelcls._meta
# print(m.fields,m.pk)
return {k.name:getattr(instance,k.name) for k in filter(fn,modelcls._meta.fields)}
# 登录接口
def login(request:HttpRequest):
try:
payload = simplejson.loads(request.body)
print(payload)
email = payload["email"]
password = payload["password"]
user = User.objects.get(email=email) # only one
print(user.password)
if bcrypt.checkpw(password,user.password.encode()):
# 验证成功
token = gen_token(user.id)
res = JsonResponse({
"user":jsonify(user,exclude=["password"]),
"token":token
}) #返回200
res.set_cookie("jwt",token)
return res
else:
return JsonResponse({"error":"用户名或密码错误"},status=400)
except Exception as e:
print(e)
#失败返回错误信息和400,所有其他错误一律用户名密码错误
return JsonResponse({"error":"用户名或密码错误"},status=400)
# 测试代码添加在user/views.py
class SimpleMiddleware1:
def __init__(self,get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self,request):
# Conde to be executed for each request before
# the view (and later middleware) are called.
print(1,'- '*30)
print(isinstance(request,HttpRequest))
print(request.GET)
print(request.POST)
print(request.body)
# 之前相当于老板本的process_request
#return HttpResponse(b'',status = 404)
response = self.get_response(request)
#Code to be executed for each request/response after
#the view is called.
print(101,'- '* 30)
return response
def process_view(self,request,view_func,view_args,view_kwargs):
print(2,"-" * 30)
print(view_func.__name__,view_args,view_kwargs)
# 观察view_func名字,说明在process_request之后,process_view之前已经做好了路径映射
return None # 继续执行其他的process_view或view
# return HttpResponse("111",status=201)
class SimpleMiddleware2:
def __init__(self,get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Conde to be executed for each request before
# the view (and later middleware) are called.
print(3,"-" * 30)
# return HttpResponse(b'',status=404)
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
print(102,"- "* 30)
return response
def process_view(self,request,view_func,view_args,view_kwargs):
print(4,"- "* 30)
print(view_func,__name__,view_args,view_kwargs)
# return None #继续执行其他的process_view或view
return HttpResponse("2222",status=201)
djweb/settings.py
文件,添加消息中间件# 修改djweb/settings.py文件,添加消息中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', #注释掉
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'user.views.SimpleMiddleware1',
'user.views.SimpleMiddleware2',
]
__call__
中get_response(request)之前代码(相当于老版本中的process_request)class BlogAuthMiddleware(object):
"""自定义中间件"""
def __init__(self,get_response):
self.get_response = get_response
# 初始化执行一次
def __call__(self,request):
# 视图函数之前执行
# 认证
print(type(request),"++++++++++++++")
print(request.GET)
print(request.POST)
print(request.body) #json数据
print("- "* 30)
response = self.get_response(request)
#视图函数之后执行
return response
# 要在setting的MIDDLEWARE中注册
用户验证装饰器
在需要认证的view函数上增强认证功能,写一个装饰器函数。谁需要认证,就在这个view函数上应用这个装饰器。
定义常量,可以在当前模块中,也可以定义在settings.py中。本次在djweb/setting.py
中添加
# 在`djweb/setting.py`中添加
#自定义常量
AUTH_EXPIRE = 8* 60 * 60 #8小时过期
AUTH_HEADER = "HTTP_JWT" #浏览器端是jwt,服务器端被改写为全大写并加HTTP_前缀
用户验证装饰器代码
# 本次写在user、views.py
# 登录验证装饰器
def authenticate(viewfunc):
def wrapper(request:HttpRequest):
# 认证越早越好
jwtheader = request.META.get(settings.AUTH_HEADER,"")
# print(request.META.keys())
# print(request.META["HTTP_COOKIE"].get(settings.AUTH_HEADER,""))
# print(request.META["HTTP_COOKIE"])
print("- ------------")
if not jwtheader:
return HttpResponse(status=401)
print(jwtheader)
# 解码
try:
payload = jwt.decode(jwtheader,settings.SECRET_KEY,algorithms=["HS256"])
# payload = "aa"
print(payload)
except Exception as e: #解码有任何异常,都不能通过认证
print(e)
return HttpResponse(status=401)
# 是否过期ToDO
print("- "*30)
try:
user_id = payload.get("user_id",0)
if user_id == 0:
return HttpResponse(status=401)
user = User.objects.get(pk=user_id)
request.user = user
except Exception as e:
print(e)
return HttpResponse(status=401)
response = viewfunc(request)
return response
return wrapper
@authenticate #在有需要的视图函数上加上此装饰器
def test(request):
print("- "*30,"test")
print(request.user)
return JsonResponse({},status=200)
# 修改原先gen_token(user_id)函数
# 对id签名
def gen_token(user_id):
# 时间戳用来判断是否过期,以便重发token或重新登录
return jwt.encode({
"user_id":user_id,
"exp":int(datetime.datetime.now().timestamp()) + settings.AUTH_EXPIRE #取整
},settings.SECRET_KEY,algorithm="HS256").decode()
from django.conf.urls import re_path
from .views import reg,login,test
urlpatterns = [
re_path(r'^$',reg), #/users/
re_path(r'^login$',login), #/users/login
re_path(r"^test$",test), #/users/test
]
JWT过期问题(pyjwt过期)
在decode的时候,默认开启过期验证,如果过期,则抛出异常
需要在payload中增加claim exp,也就是exp的键值对,记录过期的时间点
exp要求是一个整数int的时间戳,或时间
exp键值对存在,才会进行过期校验
测试
import jwt
import datetime
import threading
event = threading.Event()
key = "xdd"
#在jwt的payload中增加exp claim
exp = int(datetime.datetime.now().timestamp()+10)
data = jwt.encode({"name":'tom',"age":20,'exp':exp},key)
print(jwt.get_unverified_header(data)) #不校验签名提取header
try:
while not event.wait(1):
print(jwt.decode(data,key)) #过期校验就会抛出异常
print(datetime.datetime.now().timestamp())
except jwt.ExpiredSignatureError as e:
print(e)
from django.views.decorators.http import require_http_methods,require_POST,require_GET
@require_http_methods(["POST"])
user/views.py
from django.http import HttpResponse,HttpRequest,HttpResponseBadRequest,JsonResponse
import simplejson
from .models import User
import jwt
import datetime
import bcrypt
from django.conf import settings
from django.views.decorators.http import require_http_methods,require_POST,require_GET
# 对id签名
def gen_token(user_id):
# 时间戳用来判断是否过期,以便重发token或重新登录
return jwt.encode({
"user_id":user_id,
"exp":int(datetime.datetime.now().timestamp()) + settings.AUTH_EXPIRE #取整
},settings.SECRET_KEY,algorithm="HS256").decode()
# 注册接口
@require_http_methods(["POST"])
def reg(request:HttpRequest):
try:
payload = simplejson.loads(request.body)
email = payload['email']
query = User.objects.filter(email=email)
print(query)
print(query.query) #查看sQL语句
if query.first():
return JsonResponse({"error":"用户已存在"},status=400)
name = payload['name']
password = payload["password"].encode()
print(email,name,password)
# 密码加密
password = bcrypt.hashpw(password,bcrypt.gensalt()).decode()
print(password)
user = User()
user.email = email
user.name = name
user.password = password
user.save()
return JsonResponse({"token":gen_token(user.id)},status=201) #创建成功返回201
except Exception as e: #有任何异常,都返回
print(e)
# 失败返回错误信息和400,所有其他错误一律用户名密码错误
return JsonResponse({"error":"用户名或密码错误"},status=400)
# 筛选所需要的字段
def jsonify(instance,allow=None,exclude=[]):
# allow优先,如果有,就使用allow指定的字段,这时候exclude无效
# allow如果为空,就全体,但要看看有exclude中的要排除
modelcls = type(instance)
if allow:
fn = (lambda x:x.name in allow)
else:
fn = (lambda x:x.name not in exclude)
# from django.db.models.options import Options
# m:Options = modelcls._meta
# print(m.fields,m.pk)
# print("----------")
return {k.name:getattr(instance,k.name) for k in filter(fn,modelcls._meta.fields)}
# 登录接口
@require_POST
def login(request:HttpRequest):
try:
payload = simplejson.loads(request.body)
print(payload)
email = payload["email"]
password = payload["password"].encode()
user = User.objects.get(email=email) # only one
print(user.password)
if bcrypt.checkpw(password,user.password.encode()):
# 验证成功
token = gen_token(user.id)
res = JsonResponse({
"user":jsonify(user,exclude=["password"]),
"token":token
}) #返回200
res.set_cookie("jwt",token)
return res
else:
return JsonResponse({"error":"用户名或密码错误"},status=400)
except Exception as e:
print(e)
#失败返回错误信息和400,所有其他错误一律用户名密码错误
return JsonResponse({"error":"用户名或密码错误"},status=400)
# 登录验证装饰器
def authenticate(viewfunc):
def wrapper(request:HttpRequest):
# 认证越早越好
jwtheader = request.META.get(settings.AUTH_HEADER,"")
# print(request.META.keys())
# print(request.META["HTTP_COOKIE"].get(settings.AUTH_HEADER,""))
# print(request.META["HTTP_COOKIE"])
print("- ------------")
if not jwtheader:
return HttpResponse(status=401)
print(jwtheader)
# 解码
try:
payload = jwt.decode(jwtheader,settings.SECRET_KEY,algorithms=["HS256"])
# payload = "aa"
print(payload)
except Exception as e: #解码有任何异常,都不能通过认证
print(e)
return HttpResponse(status=401)
# 是否过期ToDO
print("- "*30)
try:
user_id = payload.get("user_id",0)
if user_id == 0:
return HttpResponse(status=401)
user = User.objects.get(pk=user_id)
request.user = user
except Exception as e:
print(e)
return HttpResponse(status=401)
response = viewfunc(request)
return response
return wrapper
@require_POST
@authenticate #在有需要的视图函数上加上此装饰器
def test(request):
print("- "*30,"test")
print(request.user)
return JsonResponse({},status=200)