A) cookie如何認證

1. 使用者輸入使用者名稱與密碼,傳送給伺服器。

2. 伺服器驗證使用者名稱和密碼,正確的就建立一個會話(session),同時會把這個會話的ID儲存到客戶端瀏覽器中,因為儲存的地方是瀏覽器的cookie,所以這種認證方式叫做基於cookie的認證方式。

3. 後續的請求中,瀏覽器會發送會話ID到伺服器,伺服器上如果能找到對應的ID的會話,那麼伺服器就會返回需要的資料給瀏覽器。

4. 當用戶退出登入,會話會同時在客戶端和伺服器端被銷燬。

B) cookie認證方式的不足之處

1. 伺服器要為每個使用者保留session資訊,連線使用者過多會造成伺服器記憶體壓力過大。

2. 適合單一域名,不適合第三方請求。

二. token

A) token的認證過程

1. 使用者輸入使用者名稱和密碼,傳送給伺服器。

2. 伺服器驗證使用者名稱和密碼,正確的話就返回一個簽名過的token(token 可以認為就是個長長的字串),瀏覽器客戶端拿到這個token。

3. 後續每次請求中,瀏覽器會把token作為http header傳送給伺服器,伺服器驗證簽名是否有效,如果有效那麼認證就成功,可以返回客戶端需要的資料。

4. 一旦使用者退出登入,只需要客戶端銷燬token即可,伺服器端不需要任何操作。

B) token認證方式的特點

客戶端的token中自己保留有大量資訊,伺服器沒有儲存這些資訊,而只負責驗證,不必進行資料庫查詢,執行效率大大提高。

三. JWT

A) JWT介紹

1. JWT是json web token縮寫。它將使用者資訊加密到token裡,伺服器不儲存任何使用者資訊。伺服器通過使用儲存的金鑰驗證token的正確性,只要正確即通過驗證。

2. 優點是在分散式系統中,很好地解決了單點登入問題,很容易解決了session共享的問題。jwt長度較小,且可以使用URL傳輸(URL safe)。不想cookies只能在web環境起作用。 JWT可以同時使用在web環境和RESTfull的介面。 
   缺點是無法作廢已頒佈的令牌/不易應對資料過期。

B) JWT組成

JWT包含三個部分: Header頭部,Payload負載和Signature簽名。由三部分生成token,三部分之間用“.”號做分割。
列如 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header 
在Header中通常包含了兩部分:type:代表token的型別,這裡使用的是JWT型別。 alg:使用的Hash演算法,例如HMAC SHA256或RSA.
{
    "alg": "HS256",
    "typ": "JWT"
}
這會被經過base64Url編碼形成第一部分

2. Payload  
token的第二個部分是荷載資訊,它包含一些宣告Claim(實體的描述,通常是一個User資訊,還包括一些其他的元資料)
宣告分三類:
   1)Reserved Claims,這是一套預定義的宣告,並不是必須的,這是一套易於使用、操作性強的宣告。包括:iss(issuer)、exp(expiration time)、sub(subject)、aud(audience)等
   2)Plubic Claims,
   3)Private Claims,交換資訊的雙方自定義的宣告
{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
}
同樣經過Base64Url編碼後形成第二部分
3.  signature  使用header中指定的演算法將編碼後的header、編碼後的payload、一個secret進行加密。
例如使用的是HMAC SHA256演算法,大致流程類似於: HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
這個signature欄位被用來確認JWT資訊的傳送者是誰,並保證資訊沒有被修改

C) 為什麼要使用JWT

相比XML格式,JSON更加簡潔,編碼之後更小,這使得JWT比SAML更加簡潔,更加適合在HTML和HTTP環境中傳遞。 在安全性方面,SWT只能夠使用HMAC演算法和共享的對稱祕鑰進行簽名,而JWT和SAML token則可以使用X.509認證的公私祕鑰對進行簽名。與簡單的JSON相比,XML和XML數字簽名會引入複雜的安全漏洞。 因為JSON可以直接對映為物件,在大多數程式語言中都提供了JSON解析器,而XML則沒有這麼自然的文件-物件對映關係,這就使得使用JWT比SAML更方便 java json web token工具類

D) JWT的django引入

1、前端請求頭帶上token:
axios.get(this.host + '/user/', {
                    // 向後端傳遞JWT token的方法
                    headers: {
                        'Authorization': 'JWT ' + this.token
                    },
                    responseType: 'json',
                })
-------------------------------------------------------------------------------------
2、django中安裝jwt
pip install djangorestframework-jwt
3、配置,指明有效期
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
-------------------------------------------------------------------------------------
4、使用
序列化器中:
token = serializers.CharField(label='登入狀態token', read_only=True)  # 增加token欄位
fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token')  # 增加token
create方法中:
 # 補充生成記錄登入狀態的token
        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)
        user.token = token
前端js檔案中加入儲存token方法

// 記錄使用者的登入狀態
                    sessionStorage.clear();
                    localStorage.clear();
                    localStorage.token = response.data.token;

注意:此次操作使用token儲存在sessionStorage中,關閉頁面也儲存,儲存在localStorage中。
---------------------------------------------------------------------------------------
5、使用JWT進行django登入
配置路由urlpatterns = [
    url(r'authorizations/', obtain_jwt_token, name='authorizations'),
]
但是預設的返回值僅有token,我們還需在返回值中增加username和user_id。
在users/utils.py 中,建立
def jwt_response_payload_handler(token, user=None, request=None):
    """
    自定義jwt認證成功返回資料
    """
    return {
        'token': token,
        'user_id': user.id,
        'username': user.username
    }
修改配置檔案
# JWT
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
------------------------------------------------------------------------------

6、登入時使用使用者名稱或者手機號同時登入
在users/utils.py中編寫:
def get_user_by_account(account):
    """
    根據帳號獲取user物件
    :param account: 賬號,可以是使用者名稱,也可以是手機號
    :return: User物件 或者 None
    """
    try:
        if re.match('^1[345789]\d{9}$', account):
            # 帳號為手機號
            user = User.objects.get(mobile=account)
        else:
            # 帳號為使用者名稱
            user = User.objects.get(username=account)
    except User.DoesNotExist:
        return None
    else:
        return user


class UsernameMobileAuthBackend(ModelBackend):
    """
    自定義使用者名稱或手機號認證
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        user = get_user_by_account(username)
        if user is not None and user.check_password(password):
            return user


配置檔案加入:
AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
]