參考基于 Token 的身份驗(yàn)證、JSON Web Token - 在Web應(yīng)用間安全地傳遞信息、基于cookie的django-rest-jwt認(rèn)證
前兩篇是關(guān)于原理的,最后一篇是和django-restframework相關(guān)的。
JWT原理
除了使用session外,還可以使用token進(jìn)行用戶認(rèn)證(Authentication)。
一個(gè)很大的區(qū)別是,session需要在服務(wù)端存儲(chǔ)能夠通過session_id而獲取的信息,每次請(qǐng)求到達(dá)服務(wù)端時(shí),需要根據(jù)session_id這個(gè)key值,獲取存儲(chǔ)在內(nèi)存/磁盤/數(shù)據(jù)庫中的信息。而token的話,信息均在token里面,服務(wù)端只需要根據(jù)token中定義的算法進(jìn)行解析,即可獲得所需認(rèn)證信息。所以一個(gè)是memory cost,一個(gè)是time cost。
現(xiàn)在使用token比較流行。
JWT(Json Web Token)是實(shí)現(xiàn)token認(rèn)證的一種通用標(biāo)準(zhǔn)。
JWT分為三部分:header,payload,signature。中間用點(diǎn)分開,均使用base64進(jìn)行編碼,所以看起來像是這樣:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOjEsIm5iZiI6MTUyNjg4NjYzM30.CTZH48xD_TdtDZcgAd8exiCxkryXASruDCbRHsFFD5Y
header基本固定,包含token使用的類型,使用的算法等。
payload是具體信息,有些字段是標(biāo)準(zhǔn)字段,當(dāng)然也可以依據(jù)需求添加自定義的字段,非常方便。為了每次請(qǐng)求獲取的jwt token稍有不同,我會(huì)在payload中加入一些合時(shí)間有關(guān)的字段,比如exp(Expiration time,過期時(shí)間),iat(Issued at,發(fā)行時(shí)間),nbf(Not before)。
signature是把前兩部分使用base64編碼后,用"."連接,然后使用在header中定義的算法(默認(rèn)是HS256)進(jìn)行加密。此過程需要額外提供一個(gè)密鑰(secret)。
三部分拼起來就是jwt token。由于有時(shí)間信息,payload和signature總是會(huì)變的。
和Django結(jié)合
一、REST
一般而言是在restful的接口中使用jwt token,相關(guān)的Django庫是djangorestframework和djangorestframework-jwt。
需要在setting中進(jìn)行配置:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'common.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'common.authentication.CookieJSONWebTokenAuthentication',
],
}
設(shè)置項(xiàng)DEFAULT_AUTHENTICATION_CLASSES是用來進(jìn)行用戶認(rèn)證的,因?yàn)榈谌綆炷J(rèn)token保存在http header中,所以自定義一個(gè)類,將token保存到cookie中。
這個(gè)類可以參考rest_framework_jwt.authentication.JSONWebTokenAuthentication。
需要實(shí)現(xiàn)兩個(gè)方法:
-
authenticate(self, request)
邏輯是:從request中獲取token,從token中獲取payload,從payload中獲取用戶認(rèn)證信息。 -
authenticate_header(self, request)
作用是:當(dāng)需要返回401 Unauthenticated或403 Permission Denied時(shí),如何配置http header中的WWW-Authenticate項(xiàng)。
當(dāng)在rest_framework中訪問request.user時(shí),調(diào)用此方法。
具體如下。
代碼在rest_framework.request中:
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
在self._authenticate()中,self.authenticators即是在settings中設(shè)置的。
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
再往上走,會(huì)發(fā)現(xiàn)是在rest_framework.views中的APIView類中的dispatch方法來調(diào)用的。
另一個(gè)設(shè)置項(xiàng)是DEFAULT_PERMISSION_CLASSES,做的是用戶授權(quán)(Authorization)判斷,由于該設(shè)置項(xiàng)的存在,所以所有的APIView均會(huì)做用戶鑒權(quán)判斷。自定義類可參考rest_framework.permissions.IsAuthenticated,目前這個(gè)類僅做了該用戶是否已a(bǔ)uthenticated的判斷,若有其他方面權(quán)限的判斷,可以在具體的view中加裝飾器。
比如,判斷用戶是否是雇員(我的request.user是一個(gè)dict):
class IsEmployeeUser(BasePermission):
"""
Allows access only to employee users.
"""
def has_permission(self, request, view):
return request.user and request.user['is_authenticated'] and request.user['is_employee']
然后,裝飾器:
from rest_framework.decorators import permission_classes
def required(func):
return permission_classes([IsEmployeeUser])(func)
具體的授權(quán)判斷,同認(rèn)證判斷一樣,均是在rest_framework.views中的APIView類中的dispatch方法中,具體是調(diào)用check_permissions(self, request)方法,其中的self.get_permissions()就是取的settings中的設(shè)置。
因?yàn)橛心J(rèn)的授權(quán)判斷類,所以如果不需要進(jìn)行授權(quán)判斷,裝飾器中應(yīng)該這樣寫
def anonymous(func):
permission_classes([])(func)
二、HTML
使用以上定義的方法,只會(huì)對(duì)restful的接口進(jìn)行認(rèn)證和授權(quán)判斷。
若想對(duì).html或.js等也想使用jwt token,首先需要配置settings:
MIDDLEWARE = [
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'common.middleware.AuthenticationMiddleware',
...
]
在默認(rèn)的AuthenticationMiddleware下添加自定義的中間件。
代碼如下,參考的是默認(rèn)的django.contrib.auth.middleware.AuthenticationMiddleware,其實(shí)也是一個(gè)Django中間件的普通實(shí)現(xiàn):
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The svc_auth authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'svc_auth.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
user, _ = authenticator.authenticate(request=request)
if user is not None:
request.user = user
其中authenticator就是上一節(jié)的CookieJSONWebTokenAuthentication的實(shí)例。
這樣就完成了用戶認(rèn)證的判斷。
接下來是用戶授權(quán)的判斷,參考的是django.contrib.auth.decorators中的login_required。
def required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda user: user and user.get('is_authenticated', False) and user.get('is_employee', False),
login_url=login_url() if login_url else SimpleLazyObject(lambda: default_login_url('employee')),
redirect_field_name=redirect_field_name,
)
if function:
return actual_decorator(function)
return actual_decorator
這樣,在需要的view中添加@required即可。
與REST的設(shè)置不同,沒有默認(rèn)的配置,所以如果沒有添加裝飾器的話,不會(huì)去進(jìn)行授權(quán)判斷,也就是說,不需要有anonymous裝飾器。
三、關(guān)于User
如果使用的Django的User,那么以上很多都不用自定義。而如果需要使用自定義的User,那么就需要對(duì)每一個(gè)獲取user對(duì)象的地方進(jìn)行重寫。