提供用户注册功能
提供用户登录处理
提供路由配置
用户登录接口设计
POST /users/login 用户登录
请求体 application/json
{
'password':'string',
'email':'string'
}
响应
200 登录成功
400 用户名密码错误
接收用户通过POST方法提交的登录信息,提交的数据是JSON格式数据
{
"password":"abc",
"email":"xiaobai@qq.com"
}
从user表中email找出匹配的一条记录,验证密码是否正确
验证通过说明是合法用户登录,显示欢迎页面
验证失败返回错误状态码
整个过程都采用AJAX异步过程,用户提交JSON数据,服务端获取数据后处理,返回JSON
路由配置
from django.conf.urls import url
from .views import reg, login
urlpatterns = [
url(r'^reg$', reg),
url(r'^login$', login),
]
登录代码
def jsonify(instance,allow=None,exclude=[]):
# 规定哪些能输出,哪些不能
modelcls=type(instance)
if allow:
fn=(lambda x:x in allow)
else:
fn=(lambda x:x not in exclude)
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)
email=payload['email']
password=payload['password'].encode()
try:
user=User.objects.get(email=email)
except Exception as e:
pass
#这里注意get和filter返回的是不同的对象
user1=User.objects.filter(email=email)
print(user1)
if not user1.first():
return JsonResponse({'error':'未注册'},status=301)
#验证
print(1111111111)
print(user)
print(user.password,111111111)
if bcrypt.checkpw(password,user.password.encode()):
token=gen_token(user.id)
res=JsonResponse({'user':jsonify(user,exclude=['password']),
'token':token})
# res.set_cookie('jwt',token)
return res
else:
return JsonResponse({'失败':'111'},1111)
except Exception as e:
return JsonResponse({'error':'用户或密码输入错误'},status=400)
认证接口
如何获取浏览器提交的token信息
1、使用Header中的Authorization通过这个header增加token信息。通过Header发送数据,方法可以是Post、Get
2、自定义header
在Http请求头中使用X-JWT字段来发送token
认证流程
基本上所有的业务都有需要认证用户的信息
在这里比较时间戳,如果过期,就直接抛未验证成功,客户端手动后就该直接跳转到登录页。
如果没有提交user id,就直接重新登录,如果用户查到,填充user对象
Django的认证
django.contrib.auth中提供了许多方法,这里主要介绍其中的三个:
1、authenticate(**credentials)
提供了用户认证,即验证用户名以及密码是否正确
user = authentica(username=‘someone’,password=‘somepassword’)
2、login(HttpRequest, user, backend=None)
该函数接受一个HttpRequest对象,以及一个认证了的User对象
此函数使用django的session框架给某个已认证的用户附加上session id等信息。
3、logout(request)
注销用户
该函数接受一个HttpRequest对象,无返回值。
当调用该函数时,当前请求的session信息会全部清除
该用户即使没有登录,使用该函数也不会报错
还提供了一个装饰器来判断是否登录django.contrib.auth.decorators.login_required
本项目使用了无session机制,且用户信息自己建表管理,所以,认证需要自己实现。
中间件技术
官方定义,在Django的request和response处理过程中,由框架提供的Hook钩子
中间件技术在1.10后发生了改变
https://docs.djangoproject.com/en/1.11/topics/http/middleware/#writing-your-own-middleware
#views.py中
class SimpleMiddleware1(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print('111111111111111111')
response = self.get_response(request)
print('222222222222222222')
# Code to be executed for each request/response after
# the view is called.
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print(333333333333333333333333)
class SimpleMiddleware2(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print('444444444444444444')
return HttpResponse('111', status=201)
response = self.get_response(request)
print('555555555555555555')
# Code to be executed for each request/response after
# the view is called.
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print(6666666666666666)
pass
#setting
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',
]
结论
- Django中间件使用的洋葱式,但有特殊的地方
- 新版中间件先在 call 中get_response(request)之前代码(相当于老版本中的process_request)
- settings中的顺序先后执行所有中间件的get_response(request)之前代码
- 全部执行完解析路径映射得到view_func
- settings中的顺序先后执行process_view部分
- return None 继续向后执行
- return HttpResponse() 就不在执行其它函数的preview函数了,此函数返回值作为浏览器端的响应
- 执行view函数,前提是前面的所有中间件process_view都返回None
- 逆序执行所有中间件的get_response(request)之后代码
- 特别注意,如果get_response(request)之前代码中return HttpResponse(),将从当前中间件立即返回给浏览器端,从洋葱中依次反弹
自定义中间件
#自定义中间件
class BlogAuthMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# 初始化执行一次
def __call__(self, request):
# 视图函数之前执行
# 认证
print(type(request),'11111111111111111111111111111111111111111111111')
print(request.GET)
print(request.POST)
print(request.body)
print('--------------------------------------------------------------')
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
中间件拦截所有视图函数,但只有一部分请求需要提供认证,所以考虑其他方法
如果绝大多数需要拦截,个别例外,采用中间件较为合适。
中间件有很多用途,适合拦截所有请求和响应。
装饰器
在需要认证的view函数上增强认证功能,写一个装饰器函数,需要认证,就在这个view函数应用这个装饰器。
定义常量
可以定义在当前模块中,也可以定义在settings.py中
AUTH_EXPIRE = 8 * 60 * 60 # 8小时过期
AUTH_HEADER = "HTTP_JWT" # 浏览器端是jwt,服务器端被改为全大写并加HTTP_前缀
def authenticate(viewfunc):
# 越早认证越好
def wrapper(request:HttpRequest):
jwtheader=request.META.get(AUTH_HEADER,'')
if not jwtheader:
return HttpResponse('jwtheader',status=401)
try:
payload=jwt.decode(jwtheader,settings.SECRET_KEY,algorithms=['HS256'])
except Exception as e:
return HttpResponse(status=401)
try:
use_id=payload.get('user_id',0)
if use_id==0:
return HttpResponse(status=401)
user=User.objects.get(pk=use_id)
request.user=user
except Exception as e:
return HttpResponse(status=401)
response=viewfunc(request)
return response
return wrapper
jwt过期问题
pyjwt过期
- 在decode的时候,默认开启过期验证,如果过期,则抛出异常
- 需要在payload中增加claim exp ,也就是exp的键值对,记录过期的时间点
- exp要求是一个整数的时间戳,或时间
- exp键值对存在,才会进行过期校验
重写jwt过期
def gen_token(user_id):
"""时间戳用来判断是否过期,以便重新发token货重新登录"""
return jwt.encode({
'user_id':user_id,
'timestamp':int(datetime.datetime.now().timestamp())+AUTH_EXPIRE
},settings.SECRET_KEY).decode()
def authenticate(viewfunc):
# 越早认证越好
def wrapper(request:HttpRequest):
jwtheader=request.META.get(AUTH_HEADER,'')
if not jwtheader:
return HttpResponse('jwtheader',status=401)
try:
payload=jwt.decode(jwtheader,settings.SECRET_KEY,algorithms=['HS256'])
except Exception as e:
return HttpResponse(status=401)
try:
use_id=payload.get('user_id',0)
if use_id==0:
return HttpResponse(status=401)
user=User.objects.get(pk=use_id)
request.user=user
except Exception as e:
return HttpResponse(status=401)
response=viewfunc(request)
return response
return wrapper
view装饰器
注册、登录函数都是只支持POST方法,可以在视图函数内部自己判断,也可以使用官方提供的装饰器指定方法
from django.views.decorators.http import require_http_methods,require_POST
@require_http_methods(['post','GET'])
@require_POST
from django.shortcuts import render
from django.http import HttpResponse,HttpRequest,JsonResponse
import simplejson
from .models import User
import jwt,bcrypt
import datetime
from django.conf import settings
from django.views.decorators.http import require_http_methods,require_POST
# Create your views here.
AUTH_EXPIRE=8*60*60
AUTH_HEADER='HTTP_JWT'#浏览器端时Jwt,服务器端被改为全大写并加HTTP_前缀
def authenticate(viewfunc):
# 越早认证越好
def wrapper(request:HttpRequest):
jwtheader=request.META.get(AUTH_HEADER,'')
if not jwtheader:
return HttpResponse('jwtheader',status=401)
try:
payload=jwt.decode(jwtheader,settings.SECRET_KEY,algorithms=['HS256'])
except Exception as e:
return HttpResponse(status=401)
try:
use_id=payload.get('user_id',0)
if use_id==0:
return HttpResponse(status=401)
user=User.objects.get(pk=use_id)
request.user=user
except Exception as e:
return HttpResponse(status=401)
response=viewfunc(request)
return response
return wrapper
def gen_token(user_id):
"""时间戳用来判断是否过期,以便重新发token货重新登录"""
return jwt.encode({
'user_id':user_id,
'timestamp':int(datetime.datetime.now().timestamp())+AUTH_EXPIRE
},settings.SECRET_KEY).decode()
@require_http_methods(['POST'])
def reg(request:HttpRequest):
try:
payload=simplejson.loads(request.body)
#这部分查找必须是能找到的,否则报错
email=payload['email']
query=User.objects.filter(email=email)
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())
print(password)
user=User()
user.email=email
user.name=name
user.password=password
user.save()
#如果正常返回json数据
return JsonResponse({'token':gen_token(user.id)},status=200)
except Exception as e:
return JsonResponse({'error':'用户名或密码错误'},status=400)
def jsonify(instance,allow=None,exclude=[]):
# 规定哪些能输出,哪些不能
modelcls=type(instance)
if allow:
fn=(lambda x:x in allow)
else:
fn=(lambda x:x not in exclude)
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)
email=payload['email']
password=payload['password'].encode()
try:
user=User.objects.get(email=email)
except Exception as e:
pass
#这里注意get和filter返回的是不同的对象
user1=User.objects.filter(email=email)
if not user1.first():
return JsonResponse({'error':'未注册'},status=301)
#验证
if bcrypt.checkpw(password,user.password.encode()):
token=gen_token(user.id)
res=JsonResponse({'user':jsonify(user,exclude=['password']),
'token':token},status=200)
res.set_cookie('jwt',token)
return res
else:
return JsonResponse({'失败':'111'},1111)
except Exception as e:
return JsonResponse({'error':'用户或密码输入错误'},status=400)
@require_POST
@authenticate
def test(request):
print(request.user)
return JsonResponse({'test':'test'},status=200)