多人使用的博客系统,此处采用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, } })
结果以下
至此,后端功能基本开发完成