文章目录
web应用中,保存状态的几种方式-token-cookie-session
无论我们选择何种语法进行开发,都会遇到一个问题,如登录之后进行状态保持,特定视图比如后台核心数据需要校验权限,等等一系列涉及账号安全问题都需要我们进行-签名验签,保持账号信息的可靠性。下面列举下我知道的几种状态保持方式。
第一种: 自定义token
用户在登录或者注册成功之后,给当前用户生成由自己的加密方式生成的token,保存到客户端上,客户端再次访问的时候,带上这个token,我们拿到这个token后再使用密钥生成now_token,和接收到的token进行对比,对比成功,即可访问正常视图,否则重定向到登录页面:
- 在我的子应用中创建一个utils模块,里面定义了两个方法,一个用来随机生成token,一个用来校验token;
from django.conf import settings
def get_token(id, salt=settings.SECRET_KEY):
import hashlib
md = hashlib.md5()
md.update(bytes(str(id), encoding='utf-8'))
md.update(bytes(salt, encoding='utf-8'))
return md.hexdigest() + '|' + str(id)
def check_token(token, salt=settings.SECRET_KEY):
ll = token.split('|')
import hashlib
md = hashlib.md5()
md.update(bytes(ll[-1], encoding='utf-8'))
md.update(bytes(salt, encoding='utf-8'))
if ll[0] == md.hexdigest():
return True
else:
return False
这里我使用了md5加密,以用户id和 自己维持的密钥进行加盐,生成token,自己的id称之为源数据,我这里使用的·settings.SECRET_KEY·称之为盐,盐可自定义,因为我们在创建django项目时会生成个独立的密钥-每个django项目的密钥都不同的SECRET_KEY。
2. 当用户填写好账号密码进行登录时,在相应视图补充token逻辑;
def post(self, request):
# 认证账户
# print(request.data)
username = request.data.get("username")
password = request.data.get("password")
if not all([username, password]):
return Response({"errmsg": "参数不全"}, status=400)
user = auth.authenticate(username=username, password=password)
# print(user.username)
if user is None:
return Response({"errmsg":"账户或者密码错误"}, status=400)
token = utils.get_token(user.pk)
# return Response({"msg":"success","username":user.username}, status=200)
return JsonResponse({"msg":"success","username":user.username, "token":token}, status=200)
前端接收到这个token,保存至本地。
这里说一点 我们浏览器的保存机制 localstorage
和sessionStorage
,localstorage
会保存到当前浏览器,用户关闭浏览器下次打开数据依旧存在, 而sessionStorage
是临时存储,当关闭当前页面或者浏览器,数据消失。
这里对我token进行说明一点,加密方式选择的是md5,用我当前用户的id先进行加密,再在这个基础上加盐再进行加密,返回给我们的十六进制的token,返回的时候我拼接了个 "|id"在后面,这样后端接受到这个token就可以根据这个id和盐生成出来token 进行对比。
第二种 django restframework自带的Token类生成的token
- 首先看 自带的Token的导包顺序:
from rest_framework.authtoken.models import Token
- 再看相应源码实现
class Token(models.Model):
"""
The default authorization token model.
"""
key = models.CharField(_("Key"), max_length=40, primary_key=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name=_("User")
)
created = models.DateTimeField(_("Created"), auto_now_add=True)
class Meta:
# Work around for a bug in Django:
# https://code.djangoproject.com/ticket/19422
#
# Also see corresponding ticket:
# https://github.com/encode/django-rest-framework/issues/705
abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
verbose_name = _("Token")
verbose_name_plural = _("Tokens")
def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
return super().save(*args, **kwargs)
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()
def __str__(self):
return self.key
首先可以明确的是 这个Token类也是一个模型类,有三个字段,key,user,created分别是token值,关联用户对象和创建时间
3. 既然是模型类,那么我们在创建这个对象的语法就很简单了
token = Token.objects.create(user=user)
- 接下来是具体的实现步骤
- 先在settings配置文件中注册token
- 再进行数据库迁移,
python manage.py makemigrations
python manage.py makemigrate
会生成一张表
- 我们将账号密码传到后端,后端进行校验成功后,便可生成token,返回给前端,前端再使用
storage
保存起来即可。
后端代码如下:
def post(self, request):
# 认证账户
# print(request.data)
username = request.data.get("username")
password = request.data.get("password")
if not all([username, password]):
return Response({"errmsg": "参数不全"}, status=400)
user = auth.authenticate(username=username, password=password)
# 校验通过, 删除原来的token
old_token = Token.objects.filter(user=user)
old_token.delete()
# print(user.username)
token = Token.objects.create(user=user)
print(token)
if user is None:
return Response({"errmsg":"账户或者密码错误"}, status=400)
# return Response({"msg":"success","username":user.username}, status=200)
return JsonResponse({"msg":"success","username":user.username, "token":token.key}, status=200)
前端代码如下:
on_submit: function(){
axios.post('/users/login/', {
username: this.username,
password: this.password,
}, {
responseType: 'json',
})
.then(response => {
// 使用浏览器本地存储保存token
sessionStorage.clear();
localStorage.username = response.data.username;
localStorage.token = response.data.token;
// 扩展全局
$.ajaxSetup({
headers : {
'Authorization' : "Token " + response.data.token
}
});
this.get_user_info();
// alert("登录成功");
})
.catch(error => {
alert(error);
alert("请登陆:")
})
},
最后在具体需要登录之后才能访问的视图加上权限验证,如下:
class QueryShow(APIView):
from rest_framework.authentication import TokenAuthentication
authentication_classes = [TokenAuthentication, ]
def get(self, request):
if request.user.is_authenticated:
print("已认证")
else:
print("未认证")
goods_unpay = OrderInfo.objects.filter(user_id=1,status=1)
data = {"goods_unpay":goods_unpay}
return render(request, 'order_success.html', context=data)
在进入这个视图之前,会进行权限验证,这里我们指定了Token验证,然后我们前端访问的时候带上了这个token 'Authorization' : "Token " + response.data.token
,验证通过就表示是登录用户。
第三种-JWT
JWT由三部分组成,分别是header(头部), playload(负载), signature(签名),签名需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分signature形式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
headers说明的是使用类型和加密方式,playload说明的是公共信息如用户名,id等。
具体使用步骤如下:
(1). 安装扩展 pip install djangorestframework-jwt
(2). 在settings文件中指定配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 这里指定使用jwt验证,当用户访问具体视图进行权限验证,就会默认的使用jwt进行验证
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # 这里指定jwt-token的过期时间
}
(3). 使用jwt扩展生成token的步骤如下:
Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
(4). 用户登录,验证后生成token,返回给前端前端保存,步骤如下:
后端:
def post(self, request):
# 认证账户
# print(request.data)
username = request.data.get("username")
password = request.data.get("password")
if not all([username, password]):
return Response({"errmsg": "参数不全"}, status=400)
user = auth.authenticate(username=username, password=password)
token = user.genetate_jwt_token()
print(token)
if user is None:
return Response({"errmsg":"账户或者密码错误"}, status=400)
# return Response({"msg":"success","username":user.username}, status=200)
return JsonResponse({"msg":"success","username":user.username, "token":token}, status=200)
##### user.genetate_jwt_token() 这是我定义在User模型类中的方法
def genetate_jwt_token(self):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(self)
token = jwt_encode_handler(payload)
return token
前端
.then(response => {
// 使用浏览器本地存储保存token
sessionStorage.clear();
localStorage.username = response.data.username;
localStorage.token = response.data.token;
this.token = response.data.token;
this.get_user_info();
然后访问需要登录之后的视图 带上JWT token 如下:
axios.get('/order/query/', {
headers: {
'Authorization': 'JWT ' + this.token
},
responseType: 'json'
})
.then(response => {
// 使用浏览器本地存储保存token
// sessionStorage.clear();
// localStorage.username = response.data.username;
// localStorage.token = response.data.token;
// 扩展全局
console.log("登陆成功");
})
.catch(error => {
alert(error);
alert("请登陆:")
});
这样 前端访问到具体视图 就会采用刚在配置文件中指定的JSONWebTokenAuthentication
验证。验证通过就是登录状态的用户啦。