多人使用的博客系統,此處採用BS架構實現
博客系統,需用戶管理,博文管理php用戶管理:用戶註冊,增刪改查用戶 前端
博文管理:增刪改查博文java
須要數據庫,本次使用Mysql5.7.17,innodb存儲引擎,前端使用react框架,後端使用django框架 python
須要支持多用戶登陸,各自能夠管理本身的博文(增刪改查),管理是不公開的,可是博文是不須要登陸就能夠公開瀏覽的。mysql
建立庫並指定字符集及相關用戶和權限react
create database if not exists blog CHARACTER set utf8mb4 COLLATE utf8mb4_general_ci; grant all on blog.* to blog@localhost identified by 'blog@123Admin'; flush privileges;
上述由於本項目後端和數據庫在一個設備上,所以可以使用localhost訪問,若非一個設備,則須要將第二條的localhost修改成'%'及 nginx
grant all on blog.* to blog@'%' identified by 'blog';
查看以下 web
基本的應用建立本節再也不累贅,如需詳細狀況請看前一章節 redis
項目建立完畢目錄以下 算法
settings.py
""" Django settings for blog project. Generated by 'django-admin startproject' using Django 2.0. For more information on this file, see https://docs.djangoproject.com/en/2.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.0/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '-5n#!qq=8=49k@iikd@c46r%=iq=nu97-5#f@4d4&^x+0=s^9f' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user', 'post', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'blog.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'blog.wsgi.application' # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default':{ 'ENGINE' :'django.db.backends.mysql', 'NAME':'blog', 'USER':'blog', 'PASSWORD':'blog@123Admin', 'HOST':'localhost', 'PORT':'3306', } } # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.0/howto/static-files/ STATIC_URL = '/static/'
啓動查看以下
提供用戶註冊成立
提供用戶登陸處理
提供路由配置
用戶註冊接口實現
接受用戶經過POST方法提交的註冊信息,提交的數據是JSON格式數據
檢查email 是否已經存在於數據庫中,若是存在則返回錯誤狀態碼,如4xx,若不存在,則將用戶提交的數據存入表中。
整個過程都採用AJAX異步過程,用戶移交JSON數據,服務端獲取數據返回處理,返回JSON。
URL:/user/reg
METHOD: POST
前端的時間只能經過CSS,JS和HTML 來完成,但後端的實現可使用多種語言共同完成
請求流程
瀏覽器------nginx/LVS(處理靜態和動態分離及反向代理請求) ------ python解決動態問題(java,php等)---- react 處理項目靜態頁面問題。
兩個python項目之間的通訊經過簡單的HTTP協議暴露URL 便可完成其之間的訪問問題。
nginx 後端代理的nginx處理靜態頁面是惟一的一條路。
用戶請求先到nginx前端,後端又兩個服務,一個是nginx靜態頁面,另外一個是python。經過靜態的nginx來訪問API來進行處理,其可使用內部IP地址加端口號進行訪問,而不須要使用外部訪問處理;
django向後訪問DB,將數據整理好後返回給nginx靜態,經過react框架造成相關的JS,經過AJAX 回調在DOM樹中渲染,並顯示出來。
from django.contrib import admin from django.conf.urls import url,include # 此處引入include模塊主要用於和下層模塊之間通訊處理 urlpatterns = [ url(r'admin/', admin.site.urls), url(r'^user/',include('user.urls')) # 此處的user.urls表示是user應用下的urls文件引用 ]
include 函數參數寫 應用.路由模塊,該函數就會動態導入指定的包的模塊,從模塊中讀取urlpatterns,返回三元組
url 函數第二參數若是不是可調用對象,若是是元祖或列表,則會從路徑找中出去已匹配的部分,將剩餘部分與應用中的路由模塊的urlpatterns 進行匹配
在user應用中建立urls.py文件
以下
#!/usr/bin/poython3.6 #conding:utf-8 from django.conf.urls import url from django.http import HttpResponse,HttpRequest,JsonResponse def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 return HttpResponse(b'user.reg') urlpatterns = [ url(r'reg$',reg) # 此處reg表示的是reg函數。其能夠是函數,對象和類, ]
測試
此處是form-data方式提交數據
JSON方式提交數據以下
日誌以下
[20/Oct/2019 10:40:22] "POST /user/reg HTTP/1.1" 200 8
[20/Oct/2019 10:42:01] "POST /user/reg HTTP/1.1" 200 8
在user/models.py中建立以下代碼,其中郵箱必須惟一
from django.db import models class User(models.Model): class Meta: db_table='user' id=models.AutoField(primary_key=True) name=models.CharField(max_length=48,null=False) email=models.CharField(max_length=64,unique=True,null=False) password=models.CharField(max_length=128,null=False) createdate=models.DateTimeField(auto_now=True) # 只在建立時更新時間 def __repr__(self): return '<user name:{} id:{}>'.format(self.name,self.id) __str__=__repr__
python manage.py makemigrations
python manage.py migrate
結果以下
#!/usr/bin/poython3.6 #conding:utf-8 from django.conf.urls import url from user.views import reg #此處經過導入的方式將views中的函數導出到此處 urlpatterns = [ url(r'reg$',reg) # 此處reg表示的是reg函數。其能夠是函數,對象和類, ]
user/views.py
from django.http import HttpResponse,HttpRequest,JsonResponse def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 print ('request','------------------') print (type(request)) print (request.POST) print (request.GET) print(request.body) return HttpResponse(b'user.reg')
JSON 請求結果以下
Quit the server with CONTROL-C. request ------------------ <class 'django.core.handlers.wsgi.WSGIRequest'> <QueryDict: {}> <QueryDict: {}> b'{\n\t"name":"mysql"\n}' [20/Oct/2019 10:47:02] "POST /user/reg HTTP/1.1" 200 8
此處返回的是一個二進制的json數據
from-data提交顯示結果以下,此中方式處理必須去掉request.body
request ------------------ <class 'django.core.handlers.wsgi.WSGIRequest'> <QueryDict: {'hello': ['word']}> <QueryDict: {}> [20/Oct/2019 10:52:12] "POST /user/reg HTTP/1.1" 200 8
因爲上述返回爲二進制數據,所以須要使用JSON對其進行相關的處理操做
修改代碼以下
from django.http import HttpResponse,HttpRequest,JsonResponse import json def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 print (json.loads(request.body.decode())) # 此處必須是JSON提交方式 return HttpResponse(b'user.reg')
請求以下
請求結果以下
{'name': 'mysql'} [20/Oct/2019 10:55:19] "POST /user/reg HTTP/1.1" 200 8
from django.http import HttpResponse,HttpRequest,JsonResponse import json def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 try: payloads=json.loads(request.body.decode()) # 此處必須是JSON提交方式 print(payloads) return HttpResponse('user.reg') except Exception as e: print (e) return HttpResponse() #建立一個實例,但實例中沒有任何內容
結果以下
{'name': 'mysql'} [20/Oct/2019 11:04:58] "POST /user/reg HTTP/1.1" 200 8
simplejson 比標準庫方便好用,功能強大
pip install simplejson
瀏覽器端端提交的數據放在了請求對象的body中,須要使用simplejson解析,解析方式和json相同,但simplejson更方便 。
from django.http import HttpResponse,HttpRequest,JsonResponse import simplejson def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 try: payloads=simplejson.loads(request.body) # 此處必須是JSON提交方式 print(payloads['name']) # 獲取其中的數據 return HttpResponse('user.reg') except Exception as e: print (e) return HttpResponse() #建立一個實例,但實例中沒有任何內容
請求以下
響應數據以下
mysql [20/Oct/2019 11:20:52] "POST /user/reg HTTP/1.1" 200 8
mkdir /var/log/blog/
郵箱檢測
郵箱檢測須要查詢user表,須要使用User類的filter方法
email=email,前面是字段名,後面是變量名,查詢後返回結果,若是查詢有結果,則說明該email已經存在,返回400到前端。
用戶存儲信息
建立User 類實例,屬性存儲數據,最後調用save方法,Django默認是在save(),delete() 的時候提交事務數據,若是提交拋出任何異常,則須要捕獲其異常
異常處理
出現獲取輸入框提交信息異常,就返回異常
查詢郵箱存在,返回異常
save方法保存數據,有異常,則向外拋出,捕獲異常
注意一點,django的異常類繼承自HttpEResponse類,因此不能raise,只能return
前端經過狀態驗證碼判斷是否成功
from django.http import HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest import simplejson import logging from .models import User FORMAT="%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log') def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 print (request.POST) print (request.body) payloads=simplejson.loads(request.body) try: email=payloads['email'] query=User.objects.filter(email=email) # 此處是驗證郵箱是否存在,若存在,則直接返回 if query: return HttpResponseBadRequest('email:{} exits'.format(email)) # 此處返回一個實例,此處return 後下面的將不會被執行 name=payloads['name'] password=payloads['password'] logging.info('註冊用戶{}'.format(name)) # 此處寫入註冊用戶基本信息 # 實例化寫入數據庫 user=User() # 實例化對象 user.email=email user.password=password user.name=name try: user.save() # commit 提交數據 return JsonResponse({'userid':user.id}) # 若是提交正常。則返回此狀況 except: raise # 若異常則直接返回 except Exception as e: logging.infe(e) return HttpResponse() #建立一個實例,但實例中沒有任何內容
請求數據以下
log日誌中返回數據和數據庫數據以下
再次請求結果以下
HTTP協議是無狀態協議,爲了解決它產生了cookie和session技術
傳統的session-cookie機制
瀏覽器發起第一次請求到服務器,服務器發現瀏覽器沒有提供session id,就認爲這是第一次請求。會返回一個新的session id 給瀏覽器端,瀏覽器只要不關閉,這個session id就會隨着每一次請求從新發送給服務器端,服務器檢查找到這個sessionid ,若查到,則就認爲是同一個會話,若沒有查到,則認爲就是一個新的請求
session是會話級別的,能夠在這個會話session中建立不少數據,連接或斷開session清除,包括session id
這個session 機制還得有過時的機制,一段時間內若是沒有發起請求,認爲用戶已經斷開,就清除session,瀏覽器端也會清除相應的cookie信息
這種狀況下服務器端保存着大量的session信息,很消耗服務器的內存字段,並且若是多服務器部署,還須要考慮session共享問題,如使用redis和memchached等解決方案。
既然服務器端就是須要一個ID來表示身份,那麼不適用session也能夠建立一個ID返回給客戶端,可是,須要保證客戶端不可篡改。
服務端生成一個標識,並使用某種算法對標識簽名
服務端受到客戶端發來的標識,須要檢查簽名
這種方案的缺點是,加密,解密須要消耗CPU計算機資源,沒法讓瀏覽器本身主動檢查過時的數據以清除。這種技術成爲JWT(JSON WEB TOKEN)
JWT(json web token) 是一種採用json方式安裝傳輸信息的方式
PYJWT 是python對JW的實現,
文檔
https://pyjwt.readthedocs.io/en/latest/
包
https://pypi.org/project/PyJWT/
安裝
pip install pyjwt
左邊是加密過的東西,沒法識別,其使用的是base64編碼,等號去掉,分爲三部分,以點號斷開
第一部分 HEADER:是什麼類型,加密算法是啥
第二部分 PAYLOAD: 數據部分
第三部分 VERIFY SIGNATURE: 加密獲得簽名,這個簽名是不可逆的,其中還包含一個密碼,而在Pycharm中就有這樣一個密碼,以下
#!/usr/bin/poython3.6 #conding:utf-8 import jwt import datetime import base64 key='test' payload={'name':'demo','email':'188@123.com','password':'demo','ts':int(datetime.datetime.now().timestamp())} pwd=jwt.encode(payload,key,'HS256') HEADER,PAYLOAD,VERIFY=pwd.split(b'.') def fix(src): rem=len(src)%4 # 取餘數 return src+b'='*rem # 使用等號填充 print (base64.urlsafe_b64decode(fix(HEADER))) print (base64.urlsafe_b64decode(fix(PAYLOAD))) print (base64.urlsafe_b64decode(fix(VERIFY)))
結果以下
def encode(self, payload, key, algorithm='HS256', headers=None, json_encoder=None): # Check that we get a mapping if not isinstance(payload, Mapping): raise TypeError('Expecting a mapping object, as JWT only supports ' 'JSON objects as payloads.') # Payload for time_claim in ['exp', 'iat', 'nbf']: # Convert datetime to a intDate value in known time-format claims if isinstance(payload.get(time_claim), datetime): payload[time_claim] = timegm(payload[time_claim].utctimetuple()) json_payload = json.dumps( payload, separators=(',', ':'), cls=json_encoder ).encode('utf-8') return super(PyJWT, self).encode( json_payload, key, algorithm, headers, json_encoder )
其中會對payload進行json.dumps進行序列化,並使用utf8的編碼方式
父類中的相關方法
def encode(self, payload, key, algorithm='HS256', headers=None, json_encoder=None): segments = [] if algorithm is None: algorithm = 'none' if algorithm not in self._valid_algs: pass # Header header = {'typ': self.header_typ, 'alg': algorithm} if headers: header.update(headers) json_header = json.dumps( header, separators=(',', ':'), cls=json_encoder ).encode('utf-8') segments.append(base64url_encode(json_header)) segments.append(base64url_encode(payload)) # Segments signing_input = b'.'.join(segments) try: alg_obj = self._algorithms[algorithm] key = alg_obj.prepare_key(key) signature = alg_obj.sign(signing_input, key) except KeyError: raise NotImplementedError('Algorithm not supported') segments.append(base64url_encode(signature)) return b'.'.join(segments)
支持的算法
def get_default_algorithms(): """ Returns the algorithms that are implemented by the library. """ default_algorithms = { 'none': NoneAlgorithm(), 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256), 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384), 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512) } if has_crypto: default_algorithms.update({ 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256), 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384), 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512), 'ES256': ECAlgorithm(ECAlgorithm.SHA256), 'ES384': ECAlgorithm(ECAlgorithm.SHA384), 'ES512': ECAlgorithm(ECAlgorithm.SHA512), 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512) }) return default_algorithms
header也會被強制轉換爲二進制形式。
其中是將頭部和payload均加入segments列表中,並經過二進制的b'.'.join進行包裝,進而將其和key一塊兒經過alg_obj.sign(signing_input, key)方法進行處理後獲得的signature加入到以前的segments再次經過b'.'.join(segments)進行返回
#!/usr/bin/poython3.6 #conding:utf-8 import jwt import datetime from jwt.algorithms import get_default_algorithms import base64 key='test' payload={'name':'demo','email':'188@123.com','password':'demo','ts':int(datetime.datetime.now().timestamp())} pwd=jwt.encode(payload,key,'HS256') header,payload,sig=pwd.split(b'.') al_obj=get_default_algorithms()['HS256'] # 拿到對應算法,由於上面的是一個函數 newkey=al_obj.prepare_key(key) # 獲取到加密後的key print(newkey) # 獲取算法信息和對應的payload信息 sig_input,_,_=pwd.rpartition(b'.') # 獲取到對應的算法信息和payload信息, #此處的總體輸出結果以下 #(b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZGVtbyIsInRzIjoxNTcxNTYwNjI2LCJwYXNzd29yZCI6ImRlbW8iLCJlbWFpbCI6IjE4OEAxMjMuY29tIn0', b'.', b'XtY5v8wB0YCsX6ZDwKAMzaPwpbYbPPhTt-vgx4StB74') print(sig_input) crypat=al_obj.sign(sig_input,newkey) # 獲取新的簽名 print (base64.urlsafe_b64encode(crypat)) # 使用base64進行處理 以下 print (sig) # 原始的加密後的sig簽名內容
結果以下
簽名的獲取過程
1 經過方法get_default_algorithms 獲取對應算法的相關實例
2 經過實例的prepare_key(key) 生成新的key,newkey,及就是進行了二進制處理
3 經過sign將新的key和對應的算法進行處理,便可生成新的簽名。
由此可知,JWT 生成的token分爲三部分
1 header,有數據類型,加密算法組成
2 payload, 負責數據傳輸,通常放入python對象便可,會被JSON序列化
3 signature,簽名部分,是前面的2部分數據分別base64編碼後使用點號連接後,加密算法使用key計算好一的一個結果,再被bse64編碼,獲得簽名。
全部的數據都是明文傳輸的,只是作了base64,若是是敏感信息,請不要使用jwt
數據簽名的目的不是爲了隱藏數據,而是保證數據不被篡改,若是數據被篡改了,發回到服務器端,服務器使用本身的key再次計算即使,而後和簽名進行比較,必定對不上簽名。
使用郵箱+ 密碼方式登陸
郵箱要求惟一就好了,但密碼如何存儲
早期,密碼都是經過名爲存儲的
後來,使用了MD5存儲,可是,目前也不安全,
MD5 是不可逆的,是非對稱算法
但MD5是能夠反查出來的,窮舉的時間也不是很長MD5,MD5計算速度很快
加相同的前綴和後綴,則若窮舉出兩個密碼。則也能夠推斷處理全部密碼,加鹽,使用hash(password+salt)的結果存儲進入數據庫中,就算拿處處理密碼反查,也沒用,但若是是固定加鹽,則仍是容易被找出規律,或者從源碼中泄露,隨機加鹽,每次鹽都變,就增長了破解的難度
暴力破解,什麼密碼都不能保證不被暴力破解,例如窮舉,因此要使用慢hash算法,如bcrypt,就會讓每一次計算都很慢,都是秒級別的,這樣會致使窮舉時間過長,在密碼破解中,CPU是不能更換的,及不能實現分佈式密碼破解。
pip install bcrypt
#!/usr/bin/poython3.6 #conding:utf-8 import bcrypt import datetime password=b'123456' # 不一樣的鹽返回結果是不一樣的 print (1, bcrypt.gensalt()) print (2,bcrypt.gensalt()) # 獲取到相同的鹽,則計算結果相同 salt=bcrypt.gensalt() print ('same salt') x=bcrypt.hashpw(password,salt) print (3,x) x=bcrypt.hashpw(password,salt) print (4,x) # 不一樣的鹽結果不一樣 print('---------- different salt -----------') x=bcrypt.hashpw(password,bcrypt.gensalt()) print (5,x) x=bcrypt.hashpw(password,bcrypt.gensalt()) print (6,x) # 校驗 print(7,bcrypt.checkpw(password,x),len(x)) # 此處返回校驗結果 print(8,bcrypt.checkpw(password+b' ',x),len(x)) # 此處增長了一個空格,則致使校驗不經過 # 計算時長 start=datetime.datetime.now() y=bcrypt.hashpw(password,bcrypt.gensalt()) delta=(datetime.datetime.now()-start).total_seconds() print (9,delta) # 校驗時長 start=datetime.datetime.now() z=bcrypt.checkpw(password,x) delta=(datetime.datetime.now()-start).total_seconds() print (10,delta,z)
結果以下
從耗時看出,bcrypt加密,驗證很是耗時,所以其若使用窮舉,則很是耗時,並且攻破一個密碼,因爲鹽不同,還得窮舉另一個
鹽 b'$2b$12$F18k/9ChWWu8BUYjC2iIMO' 加密後結果 b'$2b$12$F18k/9ChWWu8BUYjC2iIMOj0Ny0GdwC.X/.2bFAAy25GgRzcpmqsy' 其中$ 是分割符 $2b$ 加密算法 12表示2^12 key expansion rounds 這是鹽 b'F18k/9ChWWu8BUYjC2iIMO',22 個字符,Base64 編碼 這裏的密文b'F18k/9ChWWu8BUYjC2iIMOj0Ny0GdwC.X/.2bFAAy25GgRzcpmqs',31個字符,Base64
from django.http import HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest import simplejson import logging from .models import User import jwt import bcrypt from blog.settings import SECRET_KEY # 獲取django中自帶的密碼 import datetime FORMAT="%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log') def get_token(user_id): # 此處的token是經過userid和時間組成的名稱,經過django默認的key來實現加密處理 return (jwt.encode({'user_id':user_id, # 此處是獲取到的token,告訴是那個用戶 'timestamp':int(datetime.datetime.now().timestamp()), # 增長時間戳 },SECRET_KEY,'HS256')).decode() def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 print (request.POST) print (request.body) payloads=simplejson.loads(request.body) try: email=payloads['email'] query=User.objects.filter(email=email) # 此處是驗證郵箱是否存在,若存在,則直接返回 if query: return HttpResponseBadRequest('email:{} exits'.format(email)) # 此處返回一個實例,此處return 後下面的將不會被執行 name=payloads['name'] password=payloads['password'] logging.info('註冊用戶{}'.format(name)) # 此處寫入註冊用戶基本信息 # 實例化寫入數據庫 user=User() # 實例化對象 user.email=email user.password=bcrypt.hashpw(password.encode(),bcrypt.gensalt()).decode() # 密碼默認是字符串格式,而bcrypt默認須要進行相關處理 #以後返回 user.name=name try: user.save() # commit 提交數據 return JsonResponse({'token':get_token(user.id)}) # 若是提交正常。則返回此狀況 except: raise # 若異常則直接返回 except Exception as e: logging.info(e) return HttpResponse() #建立一個實例,但實例中沒有任何內容
返回結果以下
提供用戶註冊處理
提供用戶登陸處理
提供用戶路由配置
接受用戶經過POST提交的登陸信息,提交的數據是JSON格式的數據
{ "email":"122@123", "password":"demo" }
從user 表中找到email 匹配的一條記錄,驗證密碼是否正確
驗證經過說明是合法用戶登陸,顯示歡迎界面
驗證失敗返回錯誤碼,如4xx整個過程採用AJAX異步過程,用戶提交JSON數據,服務端獲取數據後處理,返回JSON對象
API 地址
URL : /user/login
METHOD: POST
user/urls.py 中配置以下
#!/usr/bin/poython3.6 #conding:utf-8 from django.conf.urls import url from user.views import reg,login urlpatterns = [ url(r'reg$',reg), # 此處reg表示的是reg函數。其能夠是函數,對象和類, url(r'login$',login) ]
def login(request:HttpRequest): payload=simplejson.loads(request.body) try: email=payload['email'] query=User.objects.filter(email=email).get() print(query.id) if not query: return HttpResponseBadRequest(b'email not exist') if bcrypt.checkpw(payload['password'].encode(),query.password.encode()): #判斷密碼合法性 # 驗證經過 token=get_token(query.id) print('token',token) res=JsonResponse({ 'user':{ 'user_id':query.id, 'name':query.name, 'email':query.email, },'token':token }) return res else: return HttpResponseBadRequest(b'password is not correct') except Exception as e: logging.info(e) return HttpResponseBadRequest(b'The request parameter is not valid')
結果以下
如何獲取瀏覽器提交的token信息?
1 使用header中的Authorization
經過這個header增長token信息
經過header 發送數據,全部方法能夠是Post,Get
2 自定義header
JWT 來發送token
咱們選擇第二種方式認證
基本上全部業務都須要認證用戶的信息
在這裏比較時間戳,若是過時,則就直接拋出401未認證,客戶端受到後就該直接跳轉至登陸頁面
若是沒有提交user id,就直接從新登陸,若用戶查到了,填充user
request -> 時間戳比較 -> user id 比較,向後執行
django.contrib.auth 中提供了許多認證方式,這裏主要介紹三種
1 authenticate(**credentials)
提供了用戶認證,及驗證用戶名及密碼是否正確user=authentical(username='1234',password='1234')
2 login(HttpRequest,user,backend=None)
該函數接受一個HttpRequest對象,及一個驗證了的User對象
此函數使用django的session框架給某個已認證的用戶附加上session id 等信息
3 logout(request)
註銷用戶
該函數接受一個HttpRequest對象,無返回值
當調用該函數時,當前請求的session信息會被所有清除
該用戶即便沒有登陸,使用該函數也不會報錯還提供了一個裝飾器來判斷是否登陸django.contrib.auth.decoratores.login_required
本項目實現了無session機制,且用戶信息本身的表來進行相關的管理,所以認證是經過本身的方式實現的
官方定義,在django的request和response處理過程當中,由框架提供的hook鉤子
中間鍵技術在1.10以後發生了變化
官方參考文檔
https://docs.djangoproject.com/en/2.2/topics/http/middleware/
其至關於全局攔截器,可以攔截進來的和出去的數據
在須要認證的view函數上加強功能,寫一個裝飾器,誰須要認證,就在這個view函數上應用這個裝飾器
from django.http import HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest import simplejson import logging from .models import User import jwt import bcrypt from blog.settings import SECRET_KEY # 獲取django中自帶的密碼 import datetime FORMAT="%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log') def get_token(user_id): # 此處的token是經過userid和時間組成的名稱,經過django默認的key來實現加密處理 return (jwt.encode({'user_id':user_id, # 此處是獲取到的token,告訴是那個用戶 'timestamp':int(datetime.datetime.now().timestamp()), # 增長時間戳 },SECRET_KEY,'HS256')).decode() AUTH_EXPIRE=8*60*60 #此處是定義超時時間 def authenticate(view): def __wapper(request:HttpRequest): print (request.META) payload=request.META.get('HTTP_JWT') # 此處會加上HTTP前綴,並自動進行大寫處理 print('request',request.body) if not payload: # 此處若爲None,則表示沒拿到,則認證失敗 return HttpResponseBadRequest(b'authenticate failed') try: payload=jwt.decode(payload,SECRET_KEY,algorithms=['HS256']) print('返回數據',payload) except: return HttpResponse(status=401) current=datetime.datetime.now().timestamp() print(current,payload.get('timestamp',0)) if (current-payload.get('timestamp',0)) > AUTH_EXPIRE: return HttpResponse(status=401) try: user_id=payload.get('user_id',-1) # 獲取user_id user=User.objects.filter(pk=user_id).get() print ('user',user_id) except Exception as e: print(e) return HttpResponse(status=401) ret=view(request) return ret return __wapper def reg(request:HttpRequest): #此處臨時配置用於測試可否正常顯示 print (request.POST) print (request.body) payloads = simplejson.loads(request.body) try: email=payloads['email'] query=User.objects.filter(email=email) # 此處是驗證郵箱是否存在,若存在,則直接返回 if query: return HttpResponseBadRequest('email:{} exits'.format(email)) # 此處返回一個實例,此處return 後下面的將不會被執行 name=payloads['name'] password=payloads['password'] logging.info('註冊用戶{}'.format(name)) # 此處寫入註冊用戶基本信息 # 實例化寫入數據庫 user=User() # 實例化對象 user.email=email user.password=bcrypt.hashpw(password.encode(),bcrypt.gensalt()).decode() # 密碼默認是字符串格式,而bcrypt默認須要進行相關處理 #以後返回 user.name=name try: user.save() # commit 提交數據 return JsonResponse({'token':get_token(user.id)}) # 若是提交正常。則返回此狀況 except: raise # 若異常則直接返回 except Exception as e: logging.info(e) return HttpResponseBadRequest(b'email not exits') #建立一個實例,但實例中沒有任何內容 @authenticate def login(request:HttpRequest): payload=simplejson.loads(request.body) try: print('login------------',payload) email=payload['email'] query=User.objects.filter(email=email).get() print(query.id) if not query: return HttpResponseBadRequest(b'email not exist') if bcrypt.checkpw(payload['password'].encode(),query.password.encode()): #判斷密碼合法性 # 驗證經過 token=get_token(query.id) print('token',token) res=JsonResponse({ 'user':{ 'user_id':query.id, 'name':query.name, 'email':query.email, },'token':token }) return res else: return HttpResponseBadRequest(b'password is not correct') except Exception as e: logging.info(e) return HttpResponseBadRequest(b'The request parameter is not valid')
請求參數以下
pyjwt 支持設置過時,在decode的時候,若是過時,則直接拋出異常,須要在payload中增長clamin exp,exp 要求是一個整數int的時間戳。
from django.shortcuts import render from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest, JsonResponse import simplejson from .models import User from testdj.settings import SECRET_KEY import jwt import datetime import bcrypt # 定義時間 EXP_TIMNE = 10 * 3600 * 8 def get_token(user_id): return jwt.encode(payload={'user_id': user_id, 'exp': int(datetime.datetime.now().timestamp())+EXP_TIMNE} , key=SECRET_KEY, algorithm='HS256').decode() def authontoken(view): def __wapper(request: HttpRequest): token = request.META.get('HTTP_JWT') if token: try: payload = jwt.decode(token, SECRET_KEY, algorithm='HS256') # 此處便有處理機制來處理過時 user = User.objects.filter(pk=payload['user_id']).get() # 獲取user_id,若存在,則代表此token是當前用戶的token request.user_id = user.id# 此處獲取user_id,用於後期直接處理 print('token 合法校驗經過') except Exception as e: print(e) return HttpResponseBadRequest(b'token auth failed') else: print('未登陸過,請登陸') return view(request) return __wapper def reg(request: HttpRequest): try: payload = simplejson.loads(request.body) email = payload['email'] print(email) query = User.objects.filter(email=email) # 獲取郵箱信息 if query: # 若郵箱存在 return HttpResponseBadRequest(b'email exist') user = User() name = payload['name'] passowrd = payload['password'].encode() print(email, name, passowrd) user.name = name user.password = bcrypt.hashpw(passowrd, bcrypt.gensalt()).decode() # 獲取加密後的password信息 user.email = email try: user.save() return JsonResponse({'userinfo': { 'USER_ID': user.id, 'name': user.name, 'email': user.email, }, 'token': get_token(user.id)}) except Exception as e: print(e) return HttpResponseBadRequest(b'data insert failed') except Exception as e: print(e) return HttpResponseBadRequest(b'paraments type not legal') @authontoken def login(request: HttpRequest): try: payload = simplejson.loads(request.body) # 郵箱和密碼,而且可以獲取token,須要先判斷郵箱是否存在,若不存在,則直接報錯 email = payload['email'] print(email, '-------------------------------') user = User.objects.filter(email=email).get() if not user.id: return HttpResponseBadRequest("email :{} not exist".format(email).encode()) password = payload['password'] if bcrypt.checkpw(password.encode(), user.password.encode()): return JsonResponse({ "userinfo": { "user_id": user.id, "user_name": user.name, "user_email": user.email, }, "token": get_token(user.id) }) else: return HttpResponseBadRequest(b'password failed') except Exception as e: print(e) return HttpResponseBadRequest(b'email failed')
功能 | 函數名 | Request 方法 | 路徑 |
---|---|---|---|
發佈 (增) | pub | post | /pub |
看文章(查) | get | get | /(\d+) |
列表(分頁) | getall | get | / |
blog/urls.py配置
from django.contrib import admin from django.conf.urls import url,include # 此處引入include模塊主要用於和下層模塊之間通訊處理 urlpatterns = [ url(r'admin/', admin.site.urls), url(r'^user/',include('user.urls')), # 此處的user.urls表示是user應用下的urls文件引用 url(r'^post/',include('post.urls')) ]
post/urls.py
#!/usr/bin/poython3.6 #conding:utf-8 from django.conf.urls import url from post.views import get,getall,pub urlpatterns=[ url(r'pub',pub), url(r'^$',getall), url(r'(\d+)',get) ]
在 /blog/post/models.py中建立以下配置
from django.db import models from testapp.models import User class Post(models.Model): class Meta: db_table = 'post' id = models.AutoField(primary_key=True) # 主鍵自增 title = models.CharField(max_length=256, null=False) # 文章標題定義 pubdata = models.DateTimeField(auto_now=True) # 自動處理時間更新 author = models.ForeignKey(User, on_delete=False) # 定義外鍵 def __repr__(self): return "<Post id:{} title:{}>".format(self.id, self.title) __str_ = __repr__ class Content(models.Model): # 此處若不添加id,則系統會自動添加自增id,用於相關操做 class Meta: db_table = 'content' post = models.OneToOneField(Post, to_field='id', on_delete=False) # 一對一,此處會有一個外鍵引用post_id content = models.TextField(null=False) def __repr__(self): return "<Content {} {}>".format(self.id, self.post) __str__ = __repr__
python manage.py makemigrations
python manage.py migrate
查看結果
/blog/post/admin.py中增長以下配置
from django.contrib import admin from .models import Content, Post admin.site.register(Content) admin.site.register(Post)
查看以下
用戶從瀏覽器端提交json數據,包含title,content
提交須要認證用戶,從請求的header中驗證jwt
from django.http import HttpResponseBadRequest, HttpRequest, HttpResponse, JsonResponse from .models import Post, Content import math from user.views import authontoken import simplejson @authontoken # 此處須要先進行認證。認證經過後方可進行相關操做,其會獲取到一個user_id,經過是否存在user_id來進行處理 def pub(request: HttpRequest): try: payload = simplejson.loads(request.body) title = payload['title'] author = request.user_id post = Post() post.title = title post.author_id = author try: post.save() cont = Content() content = payload['content'] cont.content = content cont.post_id = post.id try: cont.save() return JsonResponse({"user_id": post.id}) except Exception as e: print(e) return HttpResponseBadRequest(b'con insert into failed') except Exception as e: print(e) HttpResponseBadRequest(b'post data insert failed') except Exception as e: print(e) return HttpResponseBadRequest(b'request param not auth')
結果以下
未添加token的結果
添加了token的結果
根據post_id 查看博文並返回
此處是查看,不須要認證,相關代碼以下
def get(request: HttpRequest, id): # 此處用於獲取以前配置的分組匹配的內容 print('文章ID', id) try: query = Post.objects.filter(pk=id).get() if not query: return HttpResponseBadRequest(b'article not exist') return JsonResponse({ "post": { "post_title": query.title, "author_id": query.author.id, "post_conent": query.content.content, # 經過此方式可獲取關聯的數據庫的數據 "post_user": query.author.email, 'date': query.pubdata, 'post_name': query.author.name, } }) except Exception as e: print(e) return HttpResponseBadRequest(b'article 00 not exist')
結果以下
發起get請求,經過查詢字符串http://url/post/?page=1&size=10 進行查詢處理,獲取相關分頁數據和相關基本數據
代碼以下
def getall(request: HttpRequest): try: page = int(request.GET.get('page', 1)) # 此處可獲取相關數據的值,page和size page = page if page > 0 else 1 except: page = 1 try: size = int(request.GET.get('size', 20)) size = size if size > 0 and size < 11 else 10 except: size = 10 start = (page - 1) * size # 起始數據列表值 postsall = Post.objects.all() posts = Post.objects.all()[::-1][start:page * size] # 總數據,當前頁,總頁數 count = postsall.count() # 總頁數 pages = math.ceil(count / size) # 當前頁 page = page # 當前頁數量 return JsonResponse({ "posts": [ { "post_id": post.id, "post_title": post.title, "post_name": post.author.name, } for post in posts ], "pattern": { "count": count, "pages": pages, "page": page, "size": size, } })
優化代碼,將page和size 使用同一個函數處理以下
def getall(request: HttpRequest): size=validate(request.GET,'size',int,20,lambda x,y : x if x>0 and x<20 else y) page=validate(request.GET,'page',int,1,lambda x,y : x if x>0 else y) start = (page - 1) * size # 起始數據列表值 print(size, page) postsall = Post.objects.all() posts = Post.objects.all()[::-1][start:page * size] # 總數據,當前頁,總頁數 count = postsall.count() # 總頁數 pages = math.ceil(count / size) # 當前頁 page = page # 當前頁數量 return JsonResponse({ "posts": [ { "post_id": post.id, "post_title": post.title, "post_name": post.author.name, } for post in posts ], "pattern": { "count": count, "pages": pages, "page": page, "size": size, } })
結果以下
至此,後端功能基本開發完成