pi的通信采用token + ssl,简化和方便线上脚本的调用。Django版本1.8.16,djangorestframework版本3.5.3,用了框架提供的rest_framework.authtoken.views.obtain_auth_token和rest_framework.authentication.TokenAuthentication后,发现了一个问题,前者认证通过创建token后,这个token就不会自动更新了,非常不安全,非常危险。后者验证时候是不带缓存的,需要查询数据库,由于每次请求都要验证token,请求相当频繁,感觉不是很爽。
1、实现生成的token带过期时间
首先在setting.py配置文件设置过期时间 REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES,这里设置为60分钟
#
REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES
=
60
#
|
setting.py同目录文件view.py编辑一个视图
#
#coding=utf8
import
datetime
from
django.conf
import
settings
from
rest_framework
import
status
from
rest_framework.response
import
Response
from
rest_framework.authtoken.models
import
Token
from
rest_framework.authtoken.views
import
ObtainAuthToken
EXPIRE_MINUTES
=
getattr
(settings,
'REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES'
,
1
)
class
ObtainExpiringAuthToken(ObtainAuthToken):
"""Create user token"""
def
post(
self
, request):
serializer
=
self
.serializer_class(data
=
request.data)
if
serializer.is_valid():
token, created
=
Token.objects.get_or_create(user
=
serializer.validated_data[
'user'
])
time_now
=
datetime.datetime.now()
if
created
or
token.created < time_now
-
datetime.timedelta(minutes
=
EXPIRE_MINUTES):
# Update the created time of the token to keep it valid
token.delete()
token
=
Token.objects.create(user
=
serializer.validated_data[
'user'
])
token.created
=
time_now
token.save()
return
Response({
'token'
: token.key})
return
Response(serializer.errors, status
=
status.HTTP_400_BAD_REQUEST)
obtain_expiring_auth_token
=
ObtainExpiringAuthToken.as_view()
#
|
url.py新增url用于生成用户token
#
#from rest_framework.authtoken.views import obtain_auth_token
from
.views
import
obtain_expiring_auth_token
urlpatterns
+
=
[
#url(r'^api/token/', obtain_auth_token, name='api-token'),
url(r
'^api/token/'
, obtain_expiring_auth_token, name
=
'api-token'
),
]
#
|
用curl测试接口 api/token/
#
git:(master) ✗ curl -H
"Content-Type: application/json"
-X POST -d
'{"username":"test","password":"test"}'
http:
//127
.0.0.1:9000
/api/token/
{
"token"
:
"6ff54785241f825846e4c5fca61cceb6be7f911e"
}%
#
|
然后,然后这个生成token的接口就好了。目前还有一个问题,用户就是生成一个token例如A,然后用户再也不来请求这个接口生成token,那么这个用户的token A也会一直生效且不会被更新,那么要需要结合token验证函数,来强制删除用户过期的token。
2、自定义token验证,强制删除过期的token,顺便缓存下没有过期的token
首先在setting.py文件新增全局认证类api.authentication.ExpiringTokenAuthentication替换默认的rest_framework.authentication.TokenAuthentication
#
REST_FRAMEWORK
=
{
'DEFAULT_AUTHENTICATION_CLASSES'
: [
'rest_framework.authentication.BasicAuthentication'
,
#'rest_framework.authentication.TokenAuthentication', #enable Token authentication
'api.authentication.ExpiringTokenAuthentication'
],
'PAGE_SIZE'
:
10
,
}
#
|
新建authentication.py文件,改文件在api这个目录下面。
#
#coding=utf8
import
datetime
from
django.conf
import
settings
from
rest_framework.authentication
import
TokenAuthentication
from
rest_framework
import
exceptions
from
django.utils.translation
import
ugettext_lazy as _
from
django.core.cache
import
cache
EXPIRE_MINUTES
=
getattr
(settings,
'REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES'
,
1
)
class
ExpiringTokenAuthentication(TokenAuthentication):
"""Set up token expired time"""
def
authenticate_credentials(
self
, key):
# Search token in cache
cache_user
=
cache.get(key)
if
cache_user:
return
(cache_user, key)
model
=
self
.get_model()
try
:
token
=
model.objects.select_related(
'user'
).get(key
=
key)
except
model.DoesNotExist:
raise
exceptions.AuthenticationFailed(_(
'Invalid token.'
))
if
not
token.user.is_active:
raise
exceptions.AuthenticationFailed(_(
'User inactive or deleted.'
))
time_now
=
datetime.datetime.now()
if
token.created < time_now
-
datetime.timedelta(minutes
=
EXPIRE_MINUTES):
token.delete()
raise
exceptions.AuthenticationFailed(_(
'Token has expired then delete.'
))
if
token:
# Cache token
cache.
set
(key, token.user, EXPIRE_MINUTES
*
60
)
return
(token.user, token)
#
|
然后然后,所有的功能都实现了,删除用户过期的token和缓存token减少数据库查询。