1. 用戶模型創建
通常來說既然用了django了,不會完全舍棄它的用戶模型吧,區別就是繼承類的深度。
1.1 User
直接用django自帶的User模型,如果你能夠把需求砍到這種層度,沒毛病。
1.2 AbstractUser
這一層幫你創建好了一些字段:
password, last_login, username, first_name, last_name, email, is_staff, is_active, date_joined
其中is_staff是能訪問admin的權限。
還有幾個基礎方法。
其實繼承這層一般就可以了,再新建幾列諸如手機號,頭像之類的,美滋滋。
1.3 AbstractBaseUser
更深一層的繼承
只包含
password, last_login
但是封裝了password的加密生成,一些狀態的判斷……
這是能繼承的最基礎的類了,如果還想更深的話,可能只能重寫了
最后別忘了重新指定一下用戶模型
AUTH_USER_MODEL = 'account.Account'
2. 用戶模型獲取
2.1 獲取用戶
from django.contrib.auth import get_user_model
User = get_user_model()
獲取
User.objects.get/filter
創建
User.objects.create(username="xx")
設置密碼
user = User.objects.get(xx)
user.set_password("xxxxx")
2.2 分組
from django.contrib.auth.models import Group
Group.objects.create(name="試用用戶組")
group = Group.objects.get(name="試用用戶組")
組內所有用戶:
users = group.user_set.all()
用戶加入組:
user = User.objects.get(xx)
user.groups.add(demo_group)
我這里的接口使用了django-restframework,接下來的操作先封裝好一些類
from rest_framework.views import APIView
from rest_framework import permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BaseAPIView(APIView):
permission_classes = (permissions.AllowAny, )
authentication_classes = (JSONWebTokenAuthentication, )
3. 注冊
3.1 表單
目前第一版暫不需要密碼,通過手機短信注冊。
from rest_framework import serializers
class RegisterSerializer(serializers.Serializer):
mobile_phone = serializers.IntegerField(allow_null=False)
verification_code = serializers.IntegerField()
name = serializers.CharField()
company = serializers.CharField()
3.2 控制層
寫一下整體邏輯
class Register(APIView):
serializer_class = RegisterSerializer
def post(self, request, format=None):
serializer = RegisterSerializer(data=request.data)
if not serializer.is_valid():
return render_no_match_v2(serializer.errors)
mobile_phone = serializer.data["mobile_phone"]
...
item = generate_Register_item(mobile_phone, verification_code, name, company)
return render_api_item_v2({"item": item})
3.3 服務層
整體的流程大概如圖:
這邊我們進行的是途中的api2,api1我沒有寫在上面,是一個請求短信服務商發送短信通知的接口。
需要注意的地方有一處:服務器端要保存下來請求的驗證碼與api2里用戶輸入的驗證碼匹配
當然還要考慮覆蓋(比如說用戶請求了兩次),以及這個存儲的過期時間
綜上,感覺用Redis是比較合適的。
不過,Emmmm,人手不足,怕出了問題的時候沒人有時間去維護,這里先用django的cache了
cache.set(key, value, timeout=30*60)
注:區別在于cache會隨著django的重啟而清空
接下來是注冊(api2)的service層:
def generate_Register_item(mobile_phone, verification_code, name, company):
vc = cache.get(mobile_phone)
if vc and verification_code != vc:
return "驗證碼錯誤"
User = get_user_model()
if User.objects.filter(telephone=mobile_phone):
return "該手機已注冊"
user = User.objects.create(telephone=mobile_phone, username=mobile_phone)
user.real_name = name
user.company = company
user.save()
cache.delete(mobile_phone)
return "注冊成功"
4. 登錄
4.1 表單
同樣,沒有設置密碼,采用短信服務登錄
4.2 控制層
基本如上
4.3 服務層
django是定制了login方法的
from django.contrib.auth import login
接受三個參數login(request, user, backend=None)
本質上應該是往session里寫入了一個hash值來保存登錄狀態。
這里需要注意的是登錄前是需要驗證用戶名密碼的。
密碼在數據庫中通常不是明文存儲,而是加鹽再經過一個不可逆的hash操作存入數據庫。
所以這里的驗證本質上應該是把用戶輸入的明文密碼再經過這樣的加鹽加密,然后對比兩個字符串是否相同(沒深入了解過,個人的理解),不過這里django也有封裝的方法
from django.contrib.auth import authenticate
authenticate(username=xx, password=xx)
不過本次項目里因為暫不考慮密碼,所以不需要這一步驟,直接短信驗證后login
但是會報錯,因為正常的步驟是先authenticate,再login,其中authenticate方法會給user加了一個屬性backend
隨便寫個user測試一下得到這個backend值,賦給user
user.backend='django.contrib.auth.backends.ModelBackend'
5. 注銷
這個就不寫了,跟login一樣,django還有一個logout方法
logout(request)
6. JWT
老樣子,使用一個工具之前先搞明白它的作用,以及它和其它類似工具的區別
6.1 作用
JWT全稱:Json Web Token,一種登錄狀態的令牌驗證機制。
6.2 構成
JWT分為三段,用"."來分隔。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im1hbmJ1ZyIsIm9yaWdfaWF0IjoxNTE2MzQ5ODYwLCJlbWFpbCI6IiIsImV4cCI6MTUxNjQzNjI2MCwidXNlcl9pZCI6N30.FNOGMZ_c6pD7q6ebhjcblPcQYyVelrV5-XJHLcX_ZQU
第一部分:頭部(header)
{
'typ': 'JWT',
'alg': 'HS256'
}
第二部分:載荷(payload)
第三部分:簽證(signature)
其中JWT要在服務端設置好秘鑰,簽證時候和base64后的header,payload共同生成signature
6.3 對比,差異
說到驗證登錄狀態一般都會想到Session,我們先來捋一下一般判斷登錄狀態的流程。
- Cookie
理論上來講,用戶信息可以存到Cookie里的,如果無論你還是服務方都覺得這個安全問題無所謂的話 -.-! - Session
應該是主流的方法吧,記得前年剛工作時各種面試都會問到這個。大概就是每次登錄時服務端會在數據庫里(或者Redis之類的?)存下一條記錄,然后把session返回給瀏覽器,瀏覽器保存下來,接下來的操作會拿這個session_id向服務器驗證。
至于Session可能隱藏的問題一個是搞負載均衡時如果沒把Session存到一臺服務器上可能會不斷重新登錄(Emmmmm這個都沒考慮到還是別配多服務器了),再一個就是如果流量很大,對存儲的服務器壓力過大(T.T入行尚淺,還沒經歷過什么大型的項目,這種情況下可能會把Redis也撘成集群?) - JWT
至于JWT,其實跟Session挺像的,琢磨了好久有什么區別,后來理解大概就是服務端不用存儲吧。
6.4 驗證流程
- 客戶端用用戶名和密碼請求服務端,成功后返回token
- 客戶端存下token,每次請求時放到頭部authorization里
- 服務器收到客戶端請求時先驗證token
6.5 具體實現代碼
同樣,本次項目采用django-restframework
關于具體的代碼,先弄清總共分為三個模塊:生成,驗證, 刷新
其實這個庫可以直接用
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
如果有什么特殊的需求那么解決辦法其實也很簡單,比如說一層層依次點開obtain_jwt_token,找一個合適的層次的類繼承它。
舉個栗子:
現在要為網站設計子域名,比如說產品環境叫dev-data.xxx.xxx,預覽版叫dev-preview.xxx.xxx,要為用戶設計個權限控制
那么完全可以先一層層往上找
obtain_jwt_token
>>> ObtainJSONWebToken
>>> JSONWebTokenAPIView
停,重寫JSONWebTokenAPIView
的post方法,加一層用戶組的判斷就ok了
嗯,最后注意一點,別忘了以后的每個api里都加上這個token的驗證,可以重新定義個基類
from rest_framework.views import APIView
from rest_framework import permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BaseAPIView(APIView):
permission_classes = (permissions.AllowAny, )
authentication_classes = (JSONWebTokenAuthentication, )