用户功能设计与实现

提供用户注册功能
提供用户登录处理
提供路由配置

用户登录接口设计
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)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值