# flask-blog **Repository Path**: uid_random/flask-blog ## Basic Information - **Project Name**: flask-blog - **Description**: flask写的简单的博客,发文章,看文章,点赞、收藏,评论,不能搜索文章,不能查看自己点赞、收藏的文章 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-06 - **Last Updated**: 2022-05-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README - # 部署步骤 - *每一步后面的链接是详细操作* - 将pip源换为阿里,因豆瓣的源没有虚拟环境的安装包 - 安装虚拟环境(不想用也可以不装) - > https://blog.csdn.net/fj_changing/article/details/112623084 - 进入虚拟环境(若有),安装uwsgi - > https://blog.csdn.net/fj_changing/article/details/115490237 - 进入虚拟环境(若有),装requirements.txt里的库,这个文件来自windows,生成文件时没装uwsgi,所以需要上一步的安装 - 将flask_uploads.py的from werkzeug import secure_filename, FileStorage改为下面的两句。若是服务器,且用了宝塔和虚拟环境,这个文件在虚拟环境文件夹的lib/python3.6/site-packages/flask_uploads.py;若是本地windows,且用了虚拟环境,这个文件在虚拟环境文件夹的Lib\site-packages\flask_uploads.py;若没用虚拟环境,则是正常环境的相应路径。 - from werkzeug.utils import secure_filename - from werkzeug.datastructures import FileStorage > - https://blog.csdn.net/sinat_28521487/article/details/105727870 > - https://blog.csdn.net/qq_39548074/article/details/104414158 > - https://www.zhihu.com/question/332335077 - 添加一些环境变量 - CACHE_REDIS_HOST,短信登录时需要用来存放验证码 - CACHE_REDIS_PASSWORD - MAIL_SERVER,用户注册激活时用来发送激活邮件 - MAIL_USERNAME - MAIL_PASSWORD,用邮箱密码登不上的话就写邮箱授权码 - FLASK_CONFIG,可选,用途见代码注释 - SECRET_KEY,同上 - > https://blog.csdn.net/fj_changing/article/details/116407529 - 在本地用pycharm启动项目,不用执行这一步。在服务器用uwsgi启动项目,需要执行这一步。 - 原因 - 我用了宝塔,且网站设置的配置文件里,静态文件设置的路径是alias /www/wwwroot/域名/app/static;,若不执行这一步,访问域名时无法加载bootstrap和jquery,浏览器的network里显示下面三个链接都404,404的原因是代码里我设置了加载本地bootstrap库,服务器在本地找这些文件时是在前面设置的静态文件的路径里找,而那里没有这些文件。本地windows在本地找这些文件时是在虚拟环境(若有)或正常环境文件夹的Lib\site-packages\flask_bootstrap\static里找,当然能找到。 - http://域名/static/bootstrap/css/bootstrap.min.css?bootstrap=3.3.7.1.dev1 - http://域名/static/bootstrap/jquery.min.js?bootstrap=3.3.7.1.dev1 - http://域名/static/bootstrap/js/bootstrap.min.js?bootstrap=3.3.7.1.dev1 - 解决404的步骤 - 根据上面的三个链接可知它需要的文件的路径,路径中的static就是前面说的自己设置的放静态文件的static文件夹,所以要往路径里放相应的文件,中间没有文件夹就创建,即在放静态文件的static文件夹里创建下面2个路径 - static/bootstrap/css - static/bootstrap/js - 进入虚拟环境(若有)或正常环境里site-packages/flask_bootstrap/static,若找不到site-packages文件夹见第5条 - 将环境里的jquery.min.js复制到上一步创建的static/bootstrap文件夹 - 将环境里的css/bootstrap.min.css复制到上一步创建的static/bootstrap/css文件夹 - 将环境里的js/bootstrap.min.js复制到上一步创建的static/bootstrap/js文件夹 - 将字体图标的相关文件放入服务器静态文件夹中,原因是要加载字体图标,方法同上,我是直接将本地bootstrap文件夹中static文件夹中的fonts文件夹,整个文件夹放入服务器静态文件夹中,放入后服务器中的路径为static/bootstrap/fonts - ------ # TODO - *有删除线的是已完成或已修复的* - 注册时,如果填写的信息刚好是一位已注销的用户的信息,能否注册成功?未适配这种情况 - 邮件发送验证码时,未适配收件地址不存在等异常,暂时想把发邮件放在try-except-finally里,但不知道具体异常名,也不知道邮件地址不存在算不算异常(若算,我觉得前提是邮件服务器会返回类似"邮件已/未送达"的信息;若不会返回这样的信息,那无法判断邮件地址存不存在,也就不算异常) - ~~Ajax请求接收处,先判断是否是Ajax请求,或判断request里有没有Ajax传的参数(不推荐,原因在下面),然后再执行对参数的处理,同时加上不是Ajax请求时的判断(可以跳转到指定页)。不加这些判断时,可在浏览器直接输入Ajax请求的路由,然后会报错。~~ - ~~Ajax传的参数可以伪造(不知道flask里"判断是否是Ajax请求"是否可以伪造),如,若Ajax请求是以GET发送的,则在浏览器输入Ajax接收处的路由时跟上参数和值就行,就能"正常"访问这个路由,POST同理~~ - 找回密码的功能未写,思路是给手机或邮箱发送验证码,验证成功后修改密码。若是手机验证,这与短信登录的逻辑相同;若是邮箱验证,这与修改邮箱的逻辑相同。 - ~~发布文章的页面里也有展示文章,这里头像和作者是a标签,点击后跳转到此人的所有已发文章的页面。这个功能打算做一个专门的展示文章的页面(all_article.html),再加这个功能。现在导航条中的"所有文章"页、按类型查文章的结果页、文章的评论区的用户名和头像都可点击,跳转到被点用户的所有文章页(这个页面内虽然也有用户名和头像,但不可点击,因再跳还是跳到这一页)~~ - 前端页面虽然显示收藏按钮和点赞按钮,但点击时都执行同一个方法(is_collected_or_liked),没做区分,所以收藏的同时也会点赞,反之亦然,即收藏就是点赞,点赞就是收藏。若要完善,需在models.py中修改is_collected_or_liked()、add_collect_or_like()、del_collect_or_like(),在显示文章的页面(all_article.html和detail.html和search_by_type.html)中修改if current_user.is_collected_or_liked(i.id),还有Ajax请求的url(前2个html中Ajax请求的url不同,第3个复制的第1个)。 - ~~分页显示文章时,已注销的用户的文章,虽然不显示出来,但占页。具体在article.py中注释查"占页"。已解决,换用join多表联合查询。~~ - 未登录时,点击收藏或点赞按钮时,需提示登录。 - 由上一条衍生出的。目前的代码,未登录就能看已发的文章,此时点击收藏或点赞,按钮内容会变为取消收藏、取消点赞,这不应该,因未登录时点收藏或点赞,被点按钮的文字内容不能变 - ~~没写增加阅读量的代码,文章的阅读量现在不会增加~~ - 导航条中文章类型的下拉菜单的内容和点击后的跳转链接(在父类模板myself_base.html中)是写死的,应写成根据数据库内容来显示,这样需要所有能显示导航条的路由里都要查数据库,再将查到的结果传给模板文件,所以先写死。根据数据库内容来显示的代码如下,需传给模板文件article_type变量 - ```python ``` - 模板文件中for循环里默认有loop变量,输入loop.会提示所有可用的 - loop.index,返回序号(从1开始) - loop.index0,序号(从0开始) - loop.revindex,序号倒着,循环体内最后一个是1,rev是reverse - loop.revindex0,序号倒着,循环体内最后一个是0 - loop.first,布尔类型(True/False),是否是第一个 - loop.last,布尔类型(True/False),是否是最后一个 - 未写草稿和丢失提醒的功能,写好的文章不发布就会丢失,丢失时无提醒。如数据库里还未手动添加一些'文章类型'时,即选择列表里显示"未添加任何'文章类型'";或数据库里已有一些'文章类型',选择列表里显示'请选择文章类型',但不选类型。这时富文本模式写好文章点发布,文章不会存入数据库,会丢失。丢失提醒指弹窗提示你确定离开吗,页面内容未保存;还有前面说的场景的提醒 - 未写修改、~~删除~~文章功能~~,删除的同时也要删掉文章的评论~~ - 未写对评论进行评论的功能,但前端显示的格式已准备好(detail.html中一大块div注释和下面写好的格式,写这里时未考虑分页显示评论,后来写好分页时想到准备好的这个格式不一定支持分页),差后端查询和传给前端。~~未写对评论进行分页的功能~~ - ~~学了redis后,改了短信登录、路由send_verification_code、退出登录、路由send_verification_code_to_email、路由modify_email的函数,将原来用session的地方改为用缓存,即用cache.set()、cache.get()、cache.delete()操作redis~~ - 密码登录、注册添加了验证码图片,验证码存在session里。多人访问时,验证码会存在各自的session里,不会发生后访问的人的验证码覆盖前面的人的验证码,这种方式需要在登录、注册成功后立即删除session,代码里已写。若放在redis缓存里,写cache.set('verification_code',verification_code,timeout=120)会发生前面说的覆盖的情况,因每次存入redis的键值对的键相同。暂时想到的解决方法是,前端输入用户名后,失去焦点时,用Ajax将用户名发给服务端,服务端再将用户名与验证码绑定作为键值对。但由于Ajax请求的路由和生成验证码是同一个路由,访问页面时(还未输用户名)已经生成了验证码,发送Ajax时又请求同样的路由,又生成一次验证码,虽然login.html注释里说请求同样的路由时会返回上次的结果(点击图片更换验证码处),但不知道是否适用于这里;即便适用,即Ajax请求生成的验证码与还未输用户名之前的验证码相同,也不知道验证验证码是否正确时怎么写,因目前写的是表单的自定义验证器,若用cache.get(),get()里怎么写,即往redis里存的时候键是用户名,但这里验证时怎么知道存的时候用的那个用户名。当时视频里说先用session,就不用cache了,因还得配置redis;后面的restful新闻视频里又有这种情景,说这种情景不能用cache,因cache是公共的,它需要将value绑定到一个唯一的key上,而这种情景(未输入帐号或手机号,页面上同时需要输入验证码图片里的验证码)无法确定一个唯一的key。 - 安全相关,不限于以下 - 后面的都是自己写项目时想到的,项目写完之后看到有人总结了flask漏洞,于是将链接加在开头 - https://www.jianshu.com/p/56614e46093e - https://blog.csdn.net/weixin_40002846/article/details/110756728 - ~~手动添加到session里的键值对,用完后立即删除,防止以后非法利用这个session中的信息来绕过某些验证。如生成的验证码存入session后,在需要验证码的操作(如登录或其他提交操作)完成后,立即删除session中验证码相关的键值对~~ - 手动添加的session键值对不知道能不能设置有效期(且时间不同于成功登录后保存用户状态和信息的session),防止忘了上一条中说的删除 - 对上一条的补充,写上一条时session保存手机号与验证码的键值对,现在已将它放在redis缓存中,这种方式,若输入正确的手机号与验证码,点登录后抓包改手机号,不知道能否成功登录别人的帐号 - 密码登录时,各种登录失败的情况,最好都提示'用户名或密码错误',防止确认用户名正确后暴破密码。写的时候是按真正对应的错误原因去提示了,方便测试那些登录失败的判断是否生效 - 使用FlaskForm生成的表单,自带csrf保护,可点击FlaskForm查看,或https://flask-wtf.readthedocs.io/en/stable/csrf.html或http://www.pythondoc.com/flask-wtf/csrf.html。无论是用{{ wtf.quick_form(form) }}渲染用类生成的表单form,还是用下面代码的渲染方式,在文本输入框上点审查元素,可以看到附近有隐藏的input标签,id和name都是csrf_token,value就是根据SECRET_KEY生成的唯一token,每次请求都会变,提交表单时也会提交这个value,如果与服务端的不一致,说明请求过程中数据被拦截或篡改,提交不会被通过。但手写的表单没这个保护。 - 下面代码用类生成表单uform再传给模版文件,模版文件里再写{{ uform.类的属性名.label }}:{{ uform.类的属性名 }},label就是定义类时里面各个Field里的第一个参数,即各个文本输入框前的文字说明,这种方式要实现csrf保护要手动写{{ uform.csrf_token }} - ```html
{{ uform.csrf_token }}

{{ uform.name.label }}:{{ uform.name }}{% if uform.name.errors %}{{ uform.name.errors.0 }}{% endif %}

{{ uform.password.label }}:{{ uform.password }}{% if uform.password.errors %}{{ uform.password.errors.0 }}{% endif %}

{{ uform.confirm_pwd.label }}:{{ uform.confirm_pwd }}{% if uform.confirm_pwd.errors %}{{ uform.confirm_pwd.errors.0 }}{% endif %}

{{ uform.phone.label }}:{{ uform.phone }}{% if uform.phone.errors %}{{ uform.phone.errors.0 }}{% endif %}

{{ uform.icon.label }}:{{ uform.icon }}{% if uform.icon.errors %}{{ uform.icon.errors.0 }}{% endif %}

{{ uform.recaptcha.label }}:{{ uform.recaptcha }}

``` - 除了{{ wtf.quick_form(form) }}和上面的写法,还可以用下面的写法,uform还是上面的uform,from https://flask-bootstrap-zh.readthedocs.io/zh/latest/forms.html - ```html
{{ form.hidden_tag() }} //隐藏表单域 {{ wtf.form_errors(uform, hiddens="only") }} {{ wtf.form_field(uform.name) }} {{ wtf.form_field(uform.password) }}
``` - 当前的短信登录逻辑(见代码,且是使用session的方式,已被注释掉),若用户2已注销,给用户2发验证码后,点登录,提示"用户名不存在",接着改为另一个未注销的用户的手机号,不用再点发送验证码(因写死为123456,但不点的话服务器就不生成新session,新session里存了新手机号和对应的验证码),登录提示"The CSRF tokens do not match.",不知道什么原因。一开始发送验证码里查用户时没加`User.is_deleted==False`,才有前面的问题,加了后就没了,因未注册的号码在登录时服务器不会给他发验证码 - ```python elif isinstance(form,LoginForm2): # 短信登录 if form.verification_code.data==session.get(form.phone.data): if form.phone.data in session: u=User.query.filter(User.phone==form.phone.data).first() if not u or u.is_deleted==True: session.clear() flash('手机号不存在') ``` ```python def send_verification_code(): if request.headers.get("X-Requested-With") == "XMLHttpRequest": phone=request.args.get('phone') session[phone]='123456' u=User.query.filter(User.phone==phone,User.is_deleted==False).first() if u: return jsonify(flag=200,message='验证码发送成功') else: return jsonify(flag=400,message='该号码未注册,验证码未发送') else: return render_template('main/index.html') ``` - 是否存在SQL注入漏洞 - 是否存在session重放漏洞 - js加密/混淆,防止在浏览器Sources里看到Ajax请求接口(虽然Network里也能看,https://zhidao.baidu.com/question/1739049722523333027.html) - http://www.bjhee.com/flask-ext8.html中说应对next参数值作验证,避免被URL注入攻击。next在user.py中 - 发布文章或评论时未校验标题和正文的内容(评论只有内容,无标题),防止插入恶意代码 - 是否存在越权漏洞 - 是否存在文件上传漏洞,已测试在头像上传处选择1.txt,点上传后在burp的Content-Disposition里的filename里将1.txt改为1.png,然后放行。虽然服务器返回了自定义的500错误页,但1.png已上传成功。因设置了对上传的文件重命名为随机名,且保留扩展名,所以1.png实际在服务器中是随机名.png。未修复 - 解析漏洞,若存在则可以将burp上传的恶意文件解析成脚本,https://blog.csdn.net/qq_38684504/article/details/91351851 - 是否存在XSS漏洞 - OWASP TOP10