Token:

  • Token服务器端会话技术
  • 自定义的Session
  • 如果在Web页面开发中,使用基本和Session一致
  • 如果使用移动端或客户端开发中,通常以Json形式传输,需要移动端自己存储Token,需要获取Token关联数据的时候主动传递Token。

Cookie、Session和Token的对比

  • Cookie使用更简洁,服务器压力更小,数据不是很安全
  • Session服务器要维护Session,相对安全
  • Token拥有Session的所有优点,自己维护略微麻烦,支持更多的终端。

1 为什么要用 Token?

而要回答这个问题很简单——因为它能解决问题!

可以解决哪些问题呢?

  • Token 完全由应用管理,所以它可以避开同源策略

  • Token 可以避免 CSRF 攻击(http://dwz.cn/7joLzx)

  • Token 可以是无状态的,可以在多个服务间共享

Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。

于是,又一个问题产生了:需要为 Token 设置有效期吗?

2 需要设置有效期吗?

对于这个问题,我们不妨先看两个例子。一个例子是登录密码,一般要求定期改变密码,以防止泄漏,所以密码是有有效期的;另一个例子是安全证书。SSL 安全证书都有有效期,目的是为了解决吊销的问题,对于这个问题的详细情况,来看看知乎的回答(http://dwz.cn/7joMhq)。所以无论是从安全的角度考虑,还是从吊销的角度考虑,Token 都需要设有效期。

那么有效期多长合适呢?

只能说,根据系统的安全需要,尽可能的短,但也不能短得离谱——想像一下手机的自动熄屏时间,如果设置为 10 秒钟无操作自动熄屏,再次点亮需要输入密码,会不会疯?如果你觉得不会,那就亲自试一试,设置成可以设置的最短时间,坚持一周就好(不排除有人适应这个时间,毕竟手机厂商也是有用户体验研究的)。

然后新问题产生了,如果用户在正常操作的过程中,Token 过期失效了,要求用户重新登录……用户体验岂不是很糟糕?

为了解决在操作过程不能让用户感到 Token 失效这个问题,有一种方案是在服务器端保存 Token 状态,用户每次操作都会自动刷新(推迟) Token 的过期时间——Session 就是采用这种策略来保持用户登录状态的。然而仍然存在这样一个问题,在前后端分离、单页 App 这些情况下,每秒种可能发起很多次请求,每次都去刷新过期时间会产生非常大的代价。如果 Token 的过期时间被持久化到数据库或文件,代价就更大了。所以通常为了提升效率,减少消耗,会把 Token 的过期时保存在缓存或者内存中。

还有另一种方案,使用 Refresh Token,它可以避免频繁的读写操作。这种方案中,服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用。这种方案中,服务端只需要在客户端请求更新 Token 的时候对 Refresh Token 的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token 也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。

3 Django用例

手动实现Token

需求:使用Token进行用户校验,才能进入用户主页。

(1)定义路由

urlpatterns =[
    url(r'^register/',views.register,name='register'),	# 注册
    url(r'^login/',views.login,name='login'),			# 登录
    url(r'^mine/',views.mine,name='mine')				# 进入主页
]

(2)编写html

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生注册</title>
</head>
<body>
<form action="{% url 'myToken:register' %}" method="post">
    <span>用户名:</span><input type="text" placeholder="请输入用户名" name="username">
    <br>

    <span>密码:</span><input type="text" placeholder="请输入密码" name="password">

    <br>

    <button>注册</button>
</form>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>

<form action="{% url 'myToken:login' %}" method="post">
    <span>用户名:</span><input type="text" placeholder="请输入用户名" name="username">
    <br>

    <span>密码:</span><input type="text" placeholder="请输入密码" name="password">

    <br>

    <button>登录</button>
</form>

</body>
</html>

(3)编写模型

class Student(models.Model):
    s_name = models.CharField(max_length=16,unique=True)    # 用户名唯一约束
    s_password = models.CharField(max_length=128)

    s_token = models.CharField(max_length=128)

(4)视图函数

import hashlib
import time

from django.http import HttpResponse
from django.shortcuts import render, redirect


# Create your views here.
from django.urls import reverse

from myToken.models import Student


def register(request):
	''' 注册功能 '''
    if request.method == 'GET':
        return render(request,'register.html')

    elif request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')

        try:
            student = Student()

            student.s_name = username
            student.s_password = password

            student.save()

        except Exception as e:
            return redirect(reverse('myToken:register'))

        return HttpResponse('注册成功')


def login(request):
	''' 登录功能 '''
    if request.method == 'GET':
        return render(request,'login.html')

    elif request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')

        students = Student.objects.filter(s_name=username).filter(s_password=password)

        if students.exists():
            student = students.first()

            ip = request.META.get('REMOTE_ADDR')

            token = make_token(ip=ip,username=username)

            student.s_token = token

            student.save()

            response = HttpResponse('登录成功')

            response.set_cookie('token',token)

            return response

        return redirect(reverse('myToken:login'))


def make_token(ip,username,):
	''' 自定义token '''
    # 时间
    c_time = time.ctime()

    return hashlib.new('md5',(c_time + ip + username).encode('utf-8')).hexdigest()


def mine(request):
	''' 个人主页 '''
    token = request.COOKIES.get('token')

    try:
        student = Student.objects.get(s_token=token)
    except Exception as e:
        return redirect(reverse('myToken:login'))

    return HttpResponse(student.s_name)

(5)注册并访问。


进入主页

查看token


参考资料

深入理解token