本文共 10054 字,大约阅读时间需要 33 分钟。
接收用户通过Post方法提交的注册信息,提交的数据是JSON格式数据,检查email是否已存在与数据库表中,如果存在,返回错误状态码,例如4xx,如果不存在,将用户提交的数据存入表中,整个过程都采用A JAX异步过程,用户提交JSON数据,服务端获取数据后处理,返回JSON。
路由设置:为了避免项目中的urls.py条目过多,也为了让应用自己管理路由,采用多级路由
urlpatterns = [ # 下面三个有一个即可 url(r'^admin/', admin.site.urls), # url在 2.x版本 re_path url(r'^$', index), url(r'^index$', index), url(r'^users/', include('user.urls')), # 多级路由,查看include的原码 url(r'^posts/', include('post.urls'))]
include函数参数写应用.路由模块 ,该函数就会动态导入指定的包的模块,从模块里面读取urlpatterns,返回三元
组。url函数第二参数如果不是可调用对象,如果是元组或列表,则会从路径中除去已匹配的部分,将剩余部分与应用
中的路由模块的urlpatterns进行匹配。
# user表中新建urls.pyfrom django.conf.urls import urlfrom .views import reg, test, login, logout, test_send_email# 在views中编写视图函数reg、test、logoin、logout、test_send_emailurlpatterns = [ # 下面三个有一个即可 url(r'^$', reg), url(r'^test$', test), url(r'^logout$', logout), url(r'^login$', login), url(r'^mail$', test_send_email), ]
CSRF处理:
CSRF或XSRF(Cross-site Request Forgery),即跨站请求伪造。它也被称为:one click attack/session riding,是一种对网站的恶意利用。它伪装成来自受信任用户发起请求,难以防范。
那么该怎么解决CSRF呢?
1. 关闭CSRF中间件(不推荐)
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',]
2. 表单POST
3. Ajax POST:如果使用AJAX进行POST,需要在每一次请求Header中增加自定义字段X-CSRFTOKEN,其值来自cookie 中获取的csrftoken值
JSON数据处理:simplejson 比标准库方便好用,功能强大。$ pip install simplejson
浏览器端提交的数据放在了请求对象的body中,需要使用simplejson解析,解析的方式同json模块,但是simplejson更方便。
注册代码:
# 用户注册@require_POSTdef reg(request: HttpRequest): # print(request.GET) # print(request.POST) # print(request.content_type) # print(request.body) # print(simplejson.loads(request.body)) try: payload = simplejson.loads(request.body) email = payload['email'] # 先查询email是否已经存在 u = User.objects.filter(email=email).first() # objects为默认的管理器 if u: return JsonResponse({'error': "用户名已存在"}, status=400) name = payload['name'] password = payload['password'].encode() print(email, name, password) # 写入数据库 user = User() user.email = email user.name = name user.password = bcrypt.hashpw(password, bcrypt.gensalt()).decode() print(user) # 因为没有提交到数据库,所以数据库中并没有此条记录,所以其id为None # try: # user.save() # save会自动事务提交,当然提交失败,会自动回滚;此处不需要处理异常了,因为如果此处出现异常,会直接执行下文的except # # except: # logging.info(e) # raise user.save() # 自动提交 print(user) return JsonResponse({'a': 1000}, status=201) # 如果正常,返回json数据 except Exception as e: logging.info(e) # return HttpResponseBadRequest() # 这里返回实例,这不是异常类,不能raise,python中非异常类的子类是不能raise的 return JsonResponse({'error': "用户名已存在"}, status=400)
邮箱检查:邮箱检查需要查user表,需要使用filter方法。email=email,前面是字段名email,后面是email变量。查询后返回结果,如果查询有结果,则说明该email已经存在,邮箱已经注册,返回400到前端。
用户信息存储:创建User类实例,属性存储数据,最后调用save方法。Django默认是在save()、delete()的时候事务自动提交。 如果提交抛出任何错误,则捕获此异常做相应处理。如果没有异常,则返回201,不要返回任何用户信息。之后可能需要邮箱验证、用户登录等操作。
异常处理:
Django日志:Django的日志配置在settings.py中。
# loggingLOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'DEBUG', }, },}
配置后,就可以在控制台看到执行的SQL语句。注意,必须DEBUG=True,同时level是DEBUG,否则从控制台看不到SQL语句。
Django会为模型类提供一个objects对象,它是django.db.models.manager.Manager类型,用于与数据库交互。当定义模型类的时候没有指定管理器,则Django会为模型类提供一个objects的管理器。 如果在模型类中手动指定管理器后,Django不再提供默认的objects的管理器了。 管理器是Django的模型进行数据库查询操作的接口,Django应用的每个模型都至少拥有一个管理器。
查询集:查询会返回结果的集,它是django.db.models.query.QuerySet类型。它是惰性求值,和sqlalchemy一样。结果就是查询的集。 它是可迭代对象。
限制查询集(切片):查询集对象可以直接使用索引下标的方式(不支持负索引),相当于SQL语句中的limit和offffset子句。 注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询。
filter(k1=v1).filter(k2=v2) 等价于 filter(k1=v1, k2=v2)
filter(pk=10) 这里pk指的就是主键, 不用关心主键字段名,当然也可以使用使用主键名 filter(emp_no=10)
返回单个的值:
字段查询表达式可以作为fifilter()、exclude()、get()的参数,实现where子句
语法: 属性名称__比较运算符=值
注意:属性名和运算符之间使用双下划线
虽然Django提供传入条件的方式,但是不方便,它还提供了Q对象来解决。Q对象是django.db.models.Q,可以使用&、|操作符来组成逻辑表达式。 ~ 表示not。
可使用&|和Q对象来构造复杂的逻辑表达式,过滤器函数可以使用一个或多个Q对象 ,如果混用关键字参数和Q对象,那么Q对象必须位于关键字参数的前面。所有参数都将and在一起。
更新数据:
user = User(email='test3', name='test3') # 没有主键user.save() # 这是新建user = User(id=100, email='test4', name='test4') # 有自增主键,如果不存在,则是插入user.save()user = User(id=100, email='test4', name='test4') # 有自增主键,如果存在,则是更新user.save()
update 在查询集上同时更新数据:
# 更新所有查询的结果User.objects.filter(id__gt=4).update(password='xyz') # 将pk大于4的查询结果更新,所有用户的密码修改
delete 删除查询集数据:
ret = User.objects.filter(id__gt=4).delete()print(ret) # 运行结果# DELETE FROM `user` WHERE `user`.`id` > 4; args=(4,)# (3, {'user.User': 3})
认证:HTTP协议是无状态协议,为了解决它产生了cookie和session技术。
传统的session-cookie机制:
浏览器发起第一次请求到服务器,服务器发现浏览器没有提供session id,就认为这是第一次请求,会返回一个新的session id给浏览器端。浏览器只要不关闭,这个session id就会随着每一次请求重新发给服务器端,服务器端查找这个session id,如果查到,就认为是同一个会话。如果没有查到,就认为是新的请求。
session是会话级的,服务器端还可以在这个会话session中创建很多数据session键值对。 这个session id有过期的机制,一段时间如果没有发起请求,认为用户已经断开,服务器端就清除本次会话所有 session。浏览器端也会清除相应的cookie信息。 服务器端保存着大量session信息,很消耗服务器内存,而且如果多服务器部署,可以考虑session复制集群,也可以考虑session共享的问题,比如redis、memcached等方案。
无session方案:
既然服务端就是需要一个ID来表示身份,那么不使用session也可以创建一个ID返回给客户端。但是,要保证客户 端不可篡改该信息。服务端生成一个标识,并使用某种算法对标识签名。 服务端收到客户端发来的标识,需要检查签名。 这种方案的缺点是,加密、解密需要消耗CPU计算资源,无法让浏览器自己主动检查过期的数据以清除。 这种技术称作JWT(Json WEB Token)。
JWT(Json WEB Token)是一种采用Json方式安装传输信息的方式。这次使用PyJWT,它是Python对JWT的实现。$ pip install pyjwt
import jwtimport base64import simplejsonfrom jwt import algorithmsSECRET_KEY = 'k*)_*v2%04niq0#5xc6fkl@p0pqjn2=hrm^yw3vdxloom2v7+2'payload = { 'user': 'sun', 'school': 'mag'}def add_eq(b: bytes): """为base64编码补齐等号""" r = 4 - len(b) % 4 return b + b'=' * renc = jwt.encode(payload, SECRET_KEY, algorithm="HS256") # bytesprint(enc)header, pd, sig = enc.split(b'.')print(header, pd, sig, sep='\n')print('header = ', base64.urlsafe_b64encode(header))new_pd = base64.urlsafe_b64decode(add_eq(pd))print('payload =', new_pd)print(simplejson.loads(new_pd))print('sig =', base64.urlsafe_b64encode(sig))# 根据jwt算法重新生成签名# 1 获取算法对象alg = algorithms.get_default_algorithms()['HS256']#~~~~~~~print(alg, '~~~~~~~')new_key = alg.prepare_key(SECRET_KEY)print(new_key)# 2 获取前两部分 header.payloadsigning_input, _, _ = enc.rpartition(b'.')print(signing_input)# 3 使用key得到签名signature = alg.sign(signing_input, new_key)print('+++++++++++++++++++++++++++++++++++++++++++')print(signature)print(base64.urlsafe_b64encode(signature).decode().strip("="))# wMJbLvqCEV2rMXwtf7ibrRVUYN4os8D00t9f-LfUFHo
由此,可知jwt生成的token分为三部分:
所有数据都是明文传输的,只是做了base64,如果是敏感信息,请不要使用jwt。数据签名的目的不是为了隐藏数据,而是保证数据不被篡改。如果数据篡改了,发回到服务器端,服务器使 用自己的key再计算一遍,然后进行签名比对,一定对不上签名。
Jwt使用场景:
认证:这是Jwt最常用的场景,一旦用户登录成功,就会得到Jwt,然后请求中就可以带上这个Jwt。服务器中Jwt验 证通过,就可以被允许访问资源。甚至可以在不同域名中传递,在单点登录(Single Sign On)中应用广泛。
数据交换:Jwt可以防止数据被篡改,它还可以使用公钥、私钥加密,确保请求的发送者是可信的
密码:
使用邮箱 + 密码方式登录。邮箱要求唯一就行了,但是,密码如何存储?
早期,都是明文的密码存储。后来,使用MD5存储,但是,目前也不安全,网上有很多MD5的网站,使用反查方式找到密码。 加盐,使用hash(password + salt)的结果存入数据库中,就算拿到数据库的密码反查,也没有用了。如果是固定加盐,还是容易被找到规律,或者从源码中泄露。随机加盐,每一次盐都变,就增加了破解的难度。 暴力破解,什么密码都不能保证不被暴力破解,例如穷举。所以要使用慢hash算法,例如bcrypt,就会让每一次计 算都很慢,都是秒级的,这样穷举的时间就会很长,为了一个密码破解的时间在当前CPU或者GPU的计算能力下可 能需要几十年以上。
bcrypt : $ pip install bcrypt
import bcryptimport datetimepassword = b'sqsltr520'# 每次拿盐都不一样print(1, bcrypt.gensalt())# 1 b'$2b$12$mMOjkZgJV52F8y6IOCC0EO'print(2, bcrypt.gensalt())# 2 b'$2b$12$EA5K14e7.qgobq3SFG7e7u'# 拿到的盐相同,计算得到的密文相同print('=========================')salt = bcrypt.gensalt()x = bcrypt.hashpw(password, salt)y = bcrypt.hashpw(password, salt)print(3, x)# 3 b'$2b$12$/z3KsBywn3gF1tIIXViNX.0MmHKbXqebaBRhQrIzKUv2N.E8pTknW'print(4, y)# 4 b'$2b$12$/z3KsBywn3gF1tIIXViNX.0MmHKbXqebaBRhQrIzKUv2N.E8pTknW'# 每次拿到的盐不同,生成的密文就不一样print('~~~~~~~~~~~~~~~~~~')m = bcrypt.hashpw(password, bcrypt.gensalt())n = bcrypt.hashpw(password, bcrypt.gensalt())print(5, m)# 5 b'$2b$12$wY/JiHJBNd7H73zJx/OL1OWaHyVn2TjmRzi10Ol9d2pDmOgDBbmuO'print(6, n)# 6 b'$2b$12$mDcxX4Ol3p8YpnYNjJ83o.ORqhVsz9ptxobKPum1CsuhyTO7/aS6u'# 校验print(bcrypt.checkpw(password, x), len(x)) # True 60print(bcrypt.checkpw(password + b' ', x), len(x)) # False 60# 计算时长(加密)start = datetime.datetime.now()p = bcrypt.hashpw(password, bcrypt.gensalt())delta = (datetime.datetime.now() - start).total_seconds()print(7, 'duration={}'.format(delta))# 校验时长start = datetime.datetime.now()q = bcrypt.checkpw(password, x)delta = (datetime.datetime.now() - start).total_seconds()print(8, 'duration={}'.format(delta))# 修改了密码start = datetime.datetime.now()x = bcrypt.checkpw(b'1', x)delta = (datetime.datetime.now() - start).total_seconds()print(9, 'duration={}'.format(delta))
转载地址:http://rvfvi.baihongyu.com/