【Django 014】Django2.2会话技术之Token

为了对移动应用有更好的支持,同时弥补cookie不能完成跨域认证的缺陷,会话技术的第三种,token,被引入。这一节我们一起来看看token的工作原理,同时实现一个简单的token生成和认证流程。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

操作环境

先总结下我的操作环境:

  • Centos 7
  • Python 3.7
  • Pycharm 2019.3
  • Django 2.2

因为Django长期支持版本2.2LTS和前一个长期支持版本1.11LTS有许多地方不一样,需要小心区分。

session的缺陷

先来回顾一下session认证的流程:

  1. 用户发送用户名和密码给服务器
  2. 服务器通过验证后,在当前的session中保存用户的信息,例如用户名,登陆时间,用户角色等等
  3. 同时服务器发送一个叫做sessionid的cookie给用户
  4. 随后的每次登录,用户都会通过cookie将sessionid传给服务器
  5. 服务器查询自己的记录,根据sessionid获取到用户的信息

这样导致了两个问题:

  • 首先服务器需要维护用户信息,在进行横向扩容的时候,需要将该信息持久化来共享,不太方便
  • 然后是cookie不能实现跨域的认证,同时移动端也不支持cookie

token的改进

做为以上流程的改进,token的实现有两种方式:

  • 用户的个人信息还是保存在服务端,不过不用cookie去保存这个用户的记录id,而是发给用户一串唯一的哈希码。下次用户可以通过url的查询参数,或者post内容,或者http头的Authorization字段中将这串码传递给服务端。服务端查询自己的记录或者用户信息。(这种方式和session类似,服务器还是有状态的,不过摆脱了cookie的局限)
  • 服务端不保存任何用户信息,所有用户的信息通过服务端的一个密钥进行加密,同时加入哈希值防篡改,以一串码的形式发给用户。用户以后登录也可以通过url的查询参数,或者post内容,或者http头的Authorization字段中将这串码传递给服务端。服务端通过密钥解析后获得用户的信息。(这个方法完美解决了上面session的两个问题,使得服务器无状态,例如比较流行的JWT就是用的这种方案)

网上关于JWT是否取代session的讨论很多,我个人也觉得token跟session比起来有很大的优势。但是如果是网页的话还是session实现起来更简单一点,而且因为保存有用户的信息,服务端想做一些操作就非常容易,例如修改用户角色,修改session过期时间等等。这个问题见仁见智,我们这里不做深入讨论。

token的简单实现

下面就以上面的第一种改进方法来进行一个简单的实现,了解了背后的基本逻辑,以后遇到类似JWT的框架也就很容易理解了。还是做一个简单的登录交互:

  1. 用户在register/页面输入用户名和密码进行注册,服务端查询数据库,不存在该用户就写入数据库否则注册失败
  2. 用户在login/页面输入用户名和密码进行登录,服务端验证用户名和密码,都正确就记录并返回token,否则登录失败。这里简化模型,没有考虑客户端存储token,可以是cookie或者LocalStorage)
  3. 用户在homepage/带入自己的token访问,验证token成功则显示个人主页,否则提示错误

创建数据模型

首先创建一个数据模型,存储用户姓名,密码,和token

这里只是演示,生产环境的token会存放在缓存中进行查询

class Employee(models.Model):
    e_name = models.CharField(max_length=16, unique=True)
    e_passwd = models.CharField(max_length=128)
    e_token = models.CharField(max_length=256)

之后迁移到数据库中备用

注册页面

创建路由规则和view函数如下,提供用户注册的页面

path('register/', views.register, name='register'),
def register(request):
    if request.method == 'GET':
        return render(request, 'token_register.html')
    elif request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        try:
            employee = Employee(e_name=name, e_passwd=password)  # test only, password should be saved in hash
            employee.save()
            return HttpResponse('Register Successfully')
        except Exception as e:
            return HttpResponse('User already exists')

其中在GET方法下返回的h5页面如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Register</title>
</head>
<body>
<form action="{% url 'token:register' %}" method="post">
    <label for="name">Name: </label><input type="text" name="name" id="name"><br>
    <label for="password">Password: </label><input type="password" name="password" id="password"><br>
    <input type="submit">
</form>
</body>
</html>

访问http://127.0.0.1:8000/token/register/,进行用户注册
1-register.png
注册成功
2-success.png
查看数据库中的记录发现多了一条
3-record.png

登陆页面

创建路由规则和view函数如下,提供登录的页面

path('login/', views.login, name='login'),
def login(request):
    if request.method == 'GET':
        return render(request, 'token_login.html')
    elif request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        employee = Employee.objects.filter(e_name=name).filter(e_passwd=password)
        if employee.exists():
            ip = request.META.get('REMOTE_ADDR')
            token = generate_token(ip, name)
            result = employee.first()
            result.e_token = token
            result.save()
            data = {
                'status': 200,
                'token': token,
            }
            return JsonResponse(data=data)
        else:
            data = {
                'status': 800,
            }
            return JsonResponse(data=data)

GET方法是返回的h5页面和注册几乎一样

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="{% url 'token:login' %}" method="post">
    <label for="name">Name: </label><input type="text" name="name" id="name"><br>
    <label for="password">Password: </label><input type="password" name="password" id="password"><br>
    <input type="submit">
</body>
</html>

然后是这里是重点,就是POST方法的逻辑。如果用户名和密码都正确,就生成该用户的token,存到数据库中的同时下发给用户。成功了返回200,失败返回自定义的800

企业开发中使用6xx-9xx来进行自定义状态码

Token在生成的时候,要针对当前时间戳单个ip的单个用户必须唯一,否则就不具备身份识别性。所以其实就是对ip,用户名,和当前时间戳的信息集合做一个哈希。 这里的token生成函数如下

def generate_token(ip, name):
    timestamp = int(time.time() * 1000)
    str_bfmd5 = str(timestamp) + ip + name
    token = hashlib.md5(str_bfmd5.encode('utf-8')).hexdigest()
    return token

这里使用的是hashlib中的md5,当然也可以用sha128,sha256等等方式。注意编码对象必须是bytes类型才可以。

用刚才注册的账号访问http://127.0.0.1:8000/token/login/登录
4-login.png
成功获取到返回的Json数据
5-success.png
数据库中也成功进行了记录
6-record.png

个人主页

有了token,用户在访问的时候就可以带上它,我这里采用的是url查询参数的方式进行。

创建路由和view函数如下

path('homepage/', views.homepage, name='homepage'),
def homepage(request):
    token = request.GET.get('token')
    employee = Employee.objects.filter(e_token=token)
    if employee.exists():
        return HttpResponse(employee.first().e_name)
    else:
        return HttpResponse('Token error')

然后带上上一步返回的token创建url
http://127.0.0.1:8000/token/homepage/?token=25ec0f4ad459c0a549fd85cf5ca879f4
成功访问个人主页
7-homepage.png
也可以直接用反向解析的方式从登录页面直接重定向到个人主页来

def login(request):
    if request.method == 'GET':
        return render(request, 'token_login.html')
    elif request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        employee = Employee.objects.filter(e_name=name).filter(e_passwd=password)
        if employee.exists():
            ip = request.META.get('REMOTE_ADDR')
            token = generate_token(ip, name)
            result = employee.first()
            result.e_token = token
            result.save()
            # data = {
            #     'status': 200,
            #     'token': token,
            # }
            # return JsonResponse(data=data)
            return HttpResponseRedirect(reverse('token:homepage')+'?token='+token)
        else:
            data = {
                'status': 800,
            }
            return JsonResponse(data=data)

退出登录

因为对于下发的token没有办法控制,退出登陆的时候只需要删除数据库里面和token对应的记录即可。

总结

到了这里,MTV模型的基础就了解的差不多了。从下一节开始我们一起来看看一些进阶的内容

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值