博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
博文之用户登录接口设计
阅读量:4131 次
发布时间:2019-05-25

本文共 9820 字,大约阅读时间需要 32 分钟。

1.用户登录接口设计

接收用户通过POST方法提交的登录信息,提交的数据是JSON格式数据。

user表中email找出匹配的一条记录,验证密码是否正确。验证通过说明是合法用户登录,显示欢迎页面。 验证失败返回错误状态码,例如4xx整个过程都采用A JAX异步过程,用户提交JSON数据,服务端获取数据后处理,返回JSON

路由配置:

from django.conf.urls import urlfrom .views import reg, loginurlpatterns = [    url(r'^$', reg),    url(r'^login$', login),    ]

登录代码:

@require_POST  # 只允许post请求的方式登录def login(request: HttpRequest):    try:        payload = simplejson.loads(request.body)        print(payload, '+++++++++++')        email = payload['email']        password = payload['password'].encode()        print(password, '%%%%%%%%%%', type(password))        user = User.objects.get(email=email)  # 只有一条        print(user.password, '~~~~~~~~~', type(password))        if bcrypt.checkpw(password, user.password.encode()):            # # 验证成功            # token = gen_token(user.id)            # res = JsonResponse({            #     # 'use_id': user.id,            #     # 'email': email,            #     # 'name': user.name,            #     'user': json_ify(user, exclude=('password', )),            #     'token': token            # })            # # res.set_cookie('jwt', token)            # return res            session: SessionStore = request.session            print(type(session), session)            print(session.keys())            session.set_expiry(300)  # 会话过期,单位秒            session['user_id'] = user.id            # 对于频繁需要使用的数据,使用字符串拼出来,省得还要从数据库中查询            session['user_info'] = "{} {} {}".format(user.id, user.name, user.email)            res = JsonResponse({                'user': json_ify(user, exclude=['password']),                'user_info': session['user_info']            })            return res        else:            return JsonResponse({'error': "用户名或密码错误"}, status=400)    except Exception as e:        logging.error(e)        # 失败返回错误信息和400,所有其他错误一律用户名密码错误;有时候错误信息不宜太详细        return JsonResponse({'error': "用户名或密码错误"}, status=400)

认证接口:

如何获取浏览器提交的token信息?

  • 使用Header中的Authorization,通过这个header增加token信息。 通过header发送数据,方法可以是PostGet
  • 自定义header:在Http请求头中使用X-JWT字段来发送token

认证流程:

基本上所有的业务都有需要认证用户的信息。在这里比较时间戳,如果过期,就直接抛未认证成功401,客户端收到后就该直接跳转到登录页。 如果没有提交user id,就直接重新登录。如果用户查到了,填充user对象。

request -> 时间戳比较 -> user id 比较 -> 向后执行

Django的认证:

django.contrib.auth中提供了许多方法,这里主要介绍其中的三个:

  • authenticate(**credentials)提供了用户认证,即验证用户名以及密码是否正确 user = authentica(username='someone',password='somepassword')
  • login(HttpRequest, user, backend=None),该函数接受一个HttpRequest对象,以及一个认证了的User对象 。此函数使用djangosession框架给某个已认证的用户附加上session id等信息。
  • logout(request) :注销用户 ,该函数接受一个HttpRequest对象,无返回值。 当调用该函数时,当前请求的session信息会全部清除 该用户即使没有登录,使用该函数也不会报错 还提供了一个装饰器来判断是否登录django.contrib.auth.decorators.login_required 。本项目使用了无session机制,且用户信息自己建表管理,所以,认证需要自己实现。(最后又补充了session机制如何实现)

中间件技术Middleware:

官方定义,在Djangorequestresponse处理过程中,由框架提供的hook钩子中间件技术在1.10后发生了改变,我们当前使用1.11版本,可以使用新的方式定义。

from django.http import HttpRequest, HttpResponseclass SimpleMiddleware1:    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(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(request)        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):        # Code 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(request)        print(4, '-' * 30)        print(view_func.__name__, view_args, view_kwargs)        # return None # 继续执行其它的process_view或view        return HttpResponse('2222', status=201)

 

结论:

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), '+++++++++++++++++')        print(request.GET)        print(request.POST)        print(request.body)  # json数据        print('-'*30)        response = self.get_response(request)                # 视图函数之后执行        return response# 要在setting的MIDDLEWARE中注册

中间件拦截所有视图函数,但只有一部分请求需要提供认证,所以,考虑其他方法。如果绝大多数都需要拦截,个别例外,采用中间件较为合适。 中间件有很多用途,适合拦截所有请求和响应。例如浏览器端的IP是否禁用、UserAgent分析、异常响应的统一处理。

装饰器

在需要认证的view函数上增强认证功能,写一个装饰器函数。谁需要认证,就在这个view函数上应用这个装饰器。定义常量 可以定义在当前模块中,也可以定义在settings.py中。

def authenticate(view_func):    """此装饰器的作用是用户认证和验证过期"""    def wrapper(*args):        *s, request = args  # args为元组,如果元组只有一个元素,request会先拿;如果元组有多个元素,request会拿最后一个        session: SessionStore = request.session        print(session.items(), '~~~~~~~~~~~')        payload = session  # 这么做只是不想修改下面的代码了        # print(s, '&&&&&&&&&&&')        # print(request, '$$$$$$$$$$$$$$$$$$$')        # 
$$$$$$$$$$$$$$$$$$$ # 认证,越早越好 # print('-----------') # jwt_header = request.META.get(AUTH_HEADER, '') # print(jwt_header, '*********') # 生产环境中,所有测试代码用logging.debug,所有输出使用logging.info # # if not jwt_header: # print('00000000000000000000') # return HttpResponse(status=401) # print(jwt_header, '*********') # # # 解码 # try: # payload = jwt.decode(jwt_header, settings.SECRET_KEY, algorithms=['HS256'], # options={'verify_signature': True}) # print(payload) # except Exception as e: # 解码同时验证过期,有任何异常,都不能通过认证 # logging.error(e) # print('1111111111111111') # return HttpResponse(status=401) print('-' * 30) # 查询数据库,虽然前面的验证通过,但是此用户可能已经被禁用,一定要查 user_id = payload.get('user_id', 0) if user_id == 0: print('222222222222222222222') return HttpResponse(status=401) try: user = User.objects.get(pk=user_id) request.user = user # 动态增加属性 except Exception as e: logging.error(e) return HttpResponse(status=401) res = view_func(*args) # 注意这里的*args是参数解构 return res return wrapper

JWT过期问题

  • decode的时候,默认开启过期验证,如果过期,则抛出异常
  • 需要在payload中增加claim exp,也就是exp的键值对,记录过期的时间点
  • exp要求是一个整数int的时间戳,或时间
  • exp键值对存在,才会进行过期校验
import jwtimport datetimeimport threadingevent = threading.Event()SECRET_KEY = 'k*)_*v2%04niq0#5xc6fkl@p0pqjn2=hrm^yw3vdxloom2v7+2'payload = {    'user': 'sun',    'school': 'mag',    'exp': datetime.datetime.now().timestamp() + 3  # 3秒过期}enc = jwt.encode(payload, SECRET_KEY, algorithm='HS256')print(enc)try:    while not event.wait(1):        x = jwt.decode(enc, SECRET_KEY, algorithms=['HS256'])        print(x)except Exception as e:    print(type(e), e, '~~~~~~~~~~')    # 
Signature has expired ~~~~~~~~~~

登录接口的代码:

@require_POST  # 只允许post请求的方式登录def login(request: HttpRequest):    try:        payload = simplejson.loads(request.body)        print(payload, '+++++++++++')        email = payload['email']        password = payload['password'].encode()        print(password, '%%%%%%%%%%', type(password))        user = User.objects.get(email=email)  # 只有一条        print(user.password, '~~~~~~~~~', type(password))        if bcrypt.checkpw(password, user.password.encode()):            # # 验证成功            # token = gen_token(user.id)            # res = JsonResponse({            #     # 'use_id': user.id,            #     # 'email': email,            #     # 'name': user.name,            #     'user': json_ify(user, exclude=('password', )),            #     'token': token            # })            # # res.set_cookie('jwt', token)            # return res            session: SessionStore = request.session            print(type(session), session)            print(session.keys())            session.set_expiry(300)  # 会话过期,单位秒            session['user_id'] = user.id            # 对于频繁需要使用的数据,使用字符串拼出来,省得还要从数据库中查询            session['user_info'] = "{} {} {}".format(user.id, user.name, user.email)            res = JsonResponse({                'user': json_ify(user, exclude=['password']),                'user_info': session['user_info']            })            return res        else:            return JsonResponse({'error': "用户名或密码错误"}, status=400)    except Exception as e:        logging.error(e)        # 失败返回错误信息和400,所有其他错误一律用户名密码错误;有时候错误信息不宜太详细        return JsonResponse({'error': "用户名或密码错误"}, status=400)

 

 

转载地址:http://gvfvi.baihongyu.com/

你可能感兴趣的文章
数据挖掘算法——决策树
查看>>
数据挖掘算法——关联规则挖掘算法
查看>>
Linux环境下编程(一)——进程fork()的使用
查看>>
Linux环境下编程(二)——线程基础概念
查看>>
自己动手实现数据结构——排序算法1(冒泡、插入、归并、简单选择)(C++实现)
查看>>
Linux网络编程(附1)——封装read、write
查看>>
EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES
查看>>
通过源码了解Java中的HashMap的属性细节以及扩容内幕哦!
查看>>
Java中的线程--对线程来个多方位理解!
查看>>
了解TCP连接中的3次握手与4次挥手
查看>>
快速掌握二叉树的7种遍历方式哦!!!
查看>>
删除链表中等于给定值val的所有节点。
查看>>
用JavaScrip实现选项卡切换的效果
查看>>
用javascript实现网页中表格的行的添加与删除
查看>>
用javascript实现菜单子选项的隐藏和显示
查看>>
用javascript实现发送验证码和60秒计时重试
查看>>
用javascrip实现一个简单的加减乘除计算器
查看>>
JavaScript调试技巧之console.log()详解
查看>>
CSS中 overflow:hidden 的作用
查看>>
用javascript实现网站首页轮播图效果
查看>>