Django Model欄位加密的優雅實現
早前的一篇文章Django開發密碼管理表實例有寫我們寫了個密碼管理工具來實現對密碼的管理,當時加密解密的功能在view層實現,一直運行穩定所以也沒有過多關注實現是否優雅的問題。最近要多加幾個密碼錶再次回頭看之前的程式碼發現加解密在view層實現較為繁瑣,尤其是使用了Sadmin公共庫之後view的程式碼簡潔了很多,若再在view層處理顯然不夠優雅,是時候用更優雅的方式來實現了
Sadmin增刪改查
對資料庫表的增刪改查是開發過程中最常用到的功能,之前的文章也介紹過我們通過封裝Sadmin公共庫用最為簡潔的程式碼實現了對錶的增刪改查操作,具體有多簡潔,看下邊的程式碼
class TableList(ListCreateView):
model = Table
template = 'password/table.html'
permission = {'get': 'password.select_table', 'post': 'password.create_table'}
class TableDetail(RetrieveUpdateDestroyView):
model = Table
permission = {'get': 'password.select_table', 'put': 'password.update_table',
'delete': 'password.delete_table'}
TableList
類可以實現對錶的查詢以及新建表數據,TableDetail
可以實現對錶內單條數據的查詢、修改和刪除,對應了兩條URL
path('table/', views.TableList.as_view(), name='table-list-url'),
path('table/<int:pk>/', views.TableDetail.as_view(), name='table-detail-url'),
如果在view層實現表欄位的加密的話,那就要重寫TableList
的post方法,以及TableDetail
類的put方法,非常麻煩,那有什麼更為優雅的方法呢?對錶欄位的處理最好能在表發生變化的時候來處理,直接在model層來實現顯然要比view更合適,model層來實現的話通過Django的signals或是重寫model的save方法都是不錯的選擇
至於究竟是用signals還是重寫save方法,兩者都可以實現,個人覺得對於簡單的處理邏輯採用重寫save的方式比較好,而對於複雜的處理邏輯採用signals更清晰,而對於我們這個對欄位進行加密的需求,邏輯簡單程式碼也不需要太多,直接採用重寫save的方式就好了
重寫model的save方法
對於加密解密的核心程式碼可以參考文章Django開發密碼管理表實例給出的源碼,重寫model的save方法程式碼如下
class Table(models.Model):
username = models.CharField(max_length=64, verbose_name='用戶名')
password = models.CharField(max_length=512, verbose_name='密碼')
def __str__(self):
return self.application_name
def save(self, *args, **kwargs):
_encrypt = True
if self.pk:
old_password = Table.objects.get(id=self.id)
_encrypt = False if old_password.password == self.password else True
if _encrypt:
_m = RsaCrypto().encrypt(self.password)
if _m.get('state'):
self.password = _m.get('message')
else:
raise Exception('加密失敗:' + _m.get('message'))
super(Table, self).save(*args, **kwargs)
對於密碼加密,通常會在首次新加記錄,以及更新記錄密碼發生變化的情況下進行
每當save時如何判斷是insert還是update呢?可以通過是否存在self.pk
來判斷,Django的model必須有一個欄位為主鍵,如果用戶沒有設置主鍵欄位,那麼Django會默認創建一個名為id
的欄位作主鍵,主鍵也用pk
別名來表示,所以可以通過self.pk
是否存在來判斷本次save究竟是insert還是update
當本次save為update時,我們也需要判斷密碼欄位是否發生了變化,如果沒有變化則不需要調用加密方法,判斷欄位是否變化就需要獲取欄位提交前的值,提交前的值可以通過Table.objects.get(id=self.id)
來獲取
有了以上這些資訊,那加密就水到渠成了。我們優雅的實現了欄位的加密,那對於解密呢?個人覺得同樣放在model里比寫在veiw里更合適,可以在mdel里加個decode_password
的方法
class Table(models.Model):
...
def decode_password(self):
_m = RsaCrypto().decrypt(self.password)
if _m.get('state'):
return {
'state': 1,
'message': _m.get('message'),
'username': self.username
}
else:
return {'state': 0, 'message': 'Error: ' + _m.get('message')}
需要解密時調用Model的decode_password
方法就可以了
def decode_password(request, pk):
try:
_t = Table.objects.get(id=pk)
return JsonResponse(_t.decode_password())
except Exception as e:
return JsonResponse({'state': 0, 'message': 'Error: ' + str(e)})
寫在最後
個人對程式碼有一點潔癖,實現功能也以簡單實用為主,能2行搞定的絕對不會寫3行,能有更優的解法就會毫不猶豫去重構,同時也堅決反對「又不是不能用」的說法。對於以上實現是否優雅,或是有更好的解決方法,歡迎討論