1. 用戶模型創(chuàng)建
通常來(lái)說既然用了django了,不會(huì)完全舍棄它的用戶模型吧,區(qū)別就是繼承類的深度。
1.1 User
直接用django自帶的User模型,如果你能夠把需求砍到這種層度,沒毛病。
1.2 AbstractUser
這一層幫你創(chuàng)建好了一些字段:
password, last_login, username, first_name, last_name, email, is_staff, is_active, date_joined
其中is_staff是能訪問admin的權(quán)限。
還有幾個(gè)基礎(chǔ)方法。
其實(shí)繼承這層一般就可以了,再新建幾列諸如手機(jī)號(hào),頭像之類的,美滋滋。
1.3 AbstractBaseUser
更深一層的繼承
只包含
password, last_login
但是封裝了password的加密生成,一些狀態(tài)的判斷……
這是能繼承的最基礎(chǔ)的類了,如果還想更深的話,可能只能重寫了
最后別忘了重新指定一下用戶模型
AUTH_USER_MODEL = 'account.Account'
2. 用戶模型獲取
2.1 獲取用戶
from django.contrib.auth import get_user_model
User = get_user_model()
獲取
User.objects.get/filter
創(chuàng)建
User.objects.create(username="xx")
設(shè)置密碼
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="試用用戶組")
組內(nèi)所有用戶:
users = group.user_set.all()
用戶加入組:
user = User.objects.get(xx)
user.groups.add(demo_group)
我這里的接口使用了django-restframework,接下來(lái)的操作先封裝好一些類
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. 注冊(cè)
3.1 表單
目前第一版暫不需要密碼,通過手機(jī)短信注冊(cè)。
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 服務(wù)層
整體的流程大概如圖:

這邊我們進(jìn)行的是途中的api2,api1我沒有寫在上面,是一個(gè)請(qǐng)求短信服務(wù)商發(fā)送短信通知的接口。
需要注意的地方有一處:服務(wù)器端要保存下來(lái)請(qǐng)求的驗(yàn)證碼與api2里用戶輸入的驗(yàn)證碼匹配
當(dāng)然還要考慮覆蓋(比如說用戶請(qǐng)求了兩次),以及這個(gè)存儲(chǔ)的過期時(shí)間
綜上,感覺用Redis是比較合適的。
不過,Emmmm,人手不足,怕出了問題的時(shí)候沒人有時(shí)間去維護(hù),這里先用django的cache了
cache.set(key, value, timeout=30*60)注:區(qū)別在于cache會(huì)隨著django的重啟而清空
接下來(lái)是注冊(cè)(api2)的service層:
def generate_Register_item(mobile_phone, verification_code, name, company):
vc = cache.get(mobile_phone)
if vc and verification_code != vc:
return "驗(yàn)證碼錯(cuò)誤"
User = get_user_model()
if User.objects.filter(telephone=mobile_phone):
return "該手機(jī)已注冊(cè)"
user = User.objects.create(telephone=mobile_phone, username=mobile_phone)
user.real_name = name
user.company = company
user.save()
cache.delete(mobile_phone)
return "注冊(cè)成功"
4. 登錄
4.1 表單
同樣,沒有設(shè)置密碼,采用短信服務(wù)登錄
4.2 控制層
基本如上
4.3 服務(wù)層
django是定制了login方法的
from django.contrib.auth import login
接受三個(gè)參數(shù)login(request, user, backend=None)
本質(zhì)上應(yīng)該是往session里寫入了一個(gè)hash值來(lái)保存登錄狀態(tài)。
這里需要注意的是登錄前是需要驗(yàn)證用戶名密碼的。
密碼在數(shù)據(jù)庫(kù)中通常不是明文存儲(chǔ),而是加鹽再經(jīng)過一個(gè)不可逆的hash操作存入數(shù)據(jù)庫(kù)。
所以這里的驗(yàn)證本質(zhì)上應(yīng)該是把用戶輸入的明文密碼再經(jīng)過這樣的加鹽加密,然后對(duì)比兩個(gè)字符串是否相同(沒深入了解過,個(gè)人的理解),不過這里django也有封裝的方法
from django.contrib.auth import authenticate
authenticate(username=xx, password=xx)
不過本次項(xiàng)目里因?yàn)闀翰豢紤]密碼,所以不需要這一步驟,直接短信驗(yàn)證后login
但是會(huì)報(bào)錯(cuò),因?yàn)檎5牟襟E是先authenticate,再login,其中authenticate方法會(huì)給user加了一個(gè)屬性backend
隨便寫個(gè)user測(cè)試一下得到這個(gè)backend值,賦給user
user.backend='django.contrib.auth.backends.ModelBackend'
5. 注銷
這個(gè)就不寫了,跟login一樣,django還有一個(gè)logout方法
logout(request)
6. JWT
老樣子,使用一個(gè)工具之前先搞明白它的作用,以及它和其它類似工具的區(qū)別
6.1 作用
JWT全稱:Json Web Token,一種登錄狀態(tài)的令牌驗(yàn)證機(jī)制。
6.2 構(gòu)成
JWT分為三段,用"."來(lái)分隔。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im1hbmJ1ZyIsIm9yaWdfaWF0IjoxNTE2MzQ5ODYwLCJlbWFpbCI6IiIsImV4cCI6MTUxNjQzNjI2MCwidXNlcl9pZCI6N30.FNOGMZ_c6pD7q6ebhjcblPcQYyVelrV5-XJHLcX_ZQU
第一部分:頭部(header)
{
'typ': 'JWT',
'alg': 'HS256'
}
第二部分:載荷(payload)
第三部分:簽證(signature)
其中JWT要在服務(wù)端設(shè)置好秘鑰,簽證時(shí)候和base64后的header,payload共同生成signature
6.3 對(duì)比,差異
說到驗(yàn)證登錄狀態(tài)一般都會(huì)想到Session,我們先來(lái)捋一下一般判斷登錄狀態(tài)的流程。
- Cookie
理論上來(lái)講,用戶信息可以存到Cookie里的,如果無(wú)論你還是服務(wù)方都覺得這個(gè)安全問題無(wú)所謂的話 -.-! - Session
應(yīng)該是主流的方法吧,記得前年剛工作時(shí)各種面試都會(huì)問到這個(gè)。大概就是每次登錄時(shí)服務(wù)端會(huì)在數(shù)據(jù)庫(kù)里(或者Redis之類的?)存下一條記錄,然后把session返回給瀏覽器,瀏覽器保存下來(lái),接下來(lái)的操作會(huì)拿這個(gè)session_id向服務(wù)器驗(yàn)證。
至于Session可能隱藏的問題一個(gè)是搞負(fù)載均衡時(shí)如果沒把Session存到一臺(tái)服務(wù)器上可能會(huì)不斷重新登錄(Emmmmm這個(gè)都沒考慮到還是別配多服務(wù)器了),再一個(gè)就是如果流量很大,對(duì)存儲(chǔ)的服務(wù)器壓力過大(T.T入行尚淺,還沒經(jīng)歷過什么大型的項(xiàng)目,這種情況下可能會(huì)把Redis也撘成集群?) - JWT
至于JWT,其實(shí)跟Session挺像的,琢磨了好久有什么區(qū)別,后來(lái)理解大概就是服務(wù)端不用存儲(chǔ)吧。
6.4 驗(yàn)證流程
- 客戶端用用戶名和密碼請(qǐng)求服務(wù)端,成功后返回token
- 客戶端存下token,每次請(qǐng)求時(shí)放到頭部authorization里
- 服務(wù)器收到客戶端請(qǐng)求時(shí)先驗(yàn)證token
6.5 具體實(shí)現(xiàn)代碼
同樣,本次項(xiàng)目采用django-restframework
關(guān)于具體的代碼,先弄清總共分為三個(gè)模塊:生成,驗(yàn)證, 刷新
其實(shí)這個(gè)庫(kù)可以直接用
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
如果有什么特殊的需求那么解決辦法其實(shí)也很簡(jiǎn)單,比如說一層層依次點(diǎn)開obtain_jwt_token,找一個(gè)合適的層次的類繼承它。
舉個(gè)栗子:
現(xiàn)在要為網(wǎng)站設(shè)計(jì)子域名,比如說產(chǎn)品環(huán)境叫dev-data.xxx.xxx,預(yù)覽版叫dev-preview.xxx.xxx,要為用戶設(shè)計(jì)個(gè)權(quán)限控制
那么完全可以先一層層往上找
obtain_jwt_token >>> ObtainJSONWebToken >>> JSONWebTokenAPIView
停,重寫JSONWebTokenAPIView的post方法,加一層用戶組的判斷就ok了
嗯,最后注意一點(diǎn),別忘了以后的每個(gè)api里都加上這個(gè)token的驗(yàn)證,可以重新定義個(gè)基類
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, )