用了几天的时间,写了一个简单的用户鉴权系统,同时也输出了一系列的总结文章,希望小伙伴儿们能喜欢。
这一篇将会是本系列的最后一篇,让我一起来做个收尾,GO!
知识树
1.集成短信验证码
2.用户资料维护
短信发送demo
使用twilio发短信
我这里使用twilio提供的短信功能,它提供了一个免费的短信接口,让我们可以在完全free的状态下测试短信功能,同时也有对应的python库twilio来简化开发,只需要使用pip install twilio即可使用该公司提供的各种功能。有兴趣的同学可以移步官网查看:
产生validate code
和前面产生确认邮件的token一样,这里也使用itsdangerous来加密code
1def generate_code(expiration=3600): 2 random_num = ''.join(str(i) for i in random.sample(range(0, 9), 4)) 3 s = Serializer(Config.SECRET_KEY, expiration) 4 return s.dumps({'code': random_num}) 5 6 7def decoding_code(code): 8 s = Serializer(Config.SECRET_KEY) 9 c = s.loads(code) 10 return c['code']
然后是发送短信的代码,查看官网就可以获得,很简单,不多说了。
1def sendsms(code, num): 2 account_sid = os.environ.get('A_SID') 3 auth_token = os.environ.get('A_TK') 4 mytwilio_num = os.environ.get('T_NUM') 5 client = Client(account_sid, auth_token) 6 message = client.messages.create( 7 from_=mytwilio_num, 8 body=code, 9 to=num)
集成短信功能到系统
新增两个表单,一个是发送verify code,一个是验证verify code
1class LoginSMSCodeForm(FlaskForm): 2 phonenumber = IntegerField('Your Phone Number', validators=[DataRequired()]) 3 submit = SubmitField('Send validate code') 4 5 6class LoginSMSForm(FlaskForm): 7 validatacode = IntegerField('Enter validate code') 8 submit = SubmitField('Login')
新增两个路由函数,login_codesend和login_codeverify。分别处理两个表单的提交逻辑。
login_codesend函数,会像输入的手机号发送verify code,并把产生的code传给login_codeverify函数,用于比较。
1@auth.route('/logincodesend/', methods=['GET', 'POST']) 2def login_codesend(): 3 codeform = LoginSMSCodeForm() 4 if codeform.validate_on_submit(): 5 num = codeform.phonenumber.data 6 user = UserPhone.query.filter_by(phone=num).first() 7 if user is None: 8 user = UserPhone(phone=num) 9 db.session.add(user) 10 db.session.commit() 11 code = generate_code() 12 decod_code = decoding_code(code) 13 sendsms(decod_code, '+86' + str(num)) 14 flash('Have send a validate code to your phone') 15 return redirect(url_for('.login_codeverify', code=code, num=num)) 16 return render_template('auth/logincodesend.html', form=codeform)
login_codeverify函数,比较拿到的verify code和用户输入的code,如果一样,则登陆成功。
1@auth.route('/logincodeverify/<code>/<num>', methods=['GET', 'POST']) 2def login_codeverify(code, num): 3 form = LoginSMSForm() 4 code = decoding_code(code) 5 if form.validate_on_submit(): 6 if str(form.validatacode.data) == code: 7 webuser = WebUser.query.filter_by(phone=num).first() 8 if webuser is None: 9 webuser = WebUser(username=num, phone=num, user_id=time.time(), confirmed=True) 10 db.session.add(webuser) 11 db.session.commit() 12 phoneuser = UserPhone.query.filter_by(phone=num).first() 13 if phoneuser: 14 phoneuser.user_id = webuser.user_id 15 db.session.add(phoneuser) 16 db.session.commit() 17 login_user(webuser) 18 return redirect(url_for('main.index')) 19 flash('Invalid verify code') 20 return redirect(url_for('.login_codeverify', code=code, num=num)) 21 return render_template('auth/logincodeverify.html', form=form)
添加用户资料页
gravatar用户头像
使用gravatar,它可以把用户的Email和头像关联起来,快速生成用户头像图片,其中Email地址需要经过md5加密处理。
可以使用查询字符串的形式,在url中传入参数,来获取头像图片。
参数说明
参数名 | 说明 |
s | 图片大小,单位为像素 |
r | 图片级别,可选值有“g”,“pg”,“r”和“x” |
d | 没有注册Gavatar服务的用户使用的默认图片生成方式,例如“identicon” |
fd | 强制使用默认头像 |
URL例子:
http://secure.gravatar.com/avatar/ffd5c7d7bfcc8605aaa2d259e2590112?s=128&d=identicon&r=g
添加生成头像方法
在WebUser类中,添加生成头像的方法函数
1 def gravatar(self, size=100, default='identicon', rating='g'): 2 if request.is_secure: 3 url = 'https://secure.gravatar.com/avatar' 4 else: 5 url = 'http://secure.gravatar.com/avatar' 6 email = self.email or 'test@luobo.com' 7 hash = self.avatar_hash or hashlib.md5( 8 email.lower().encode('utf-8')).hexdigest() 9 avatar = '{url}/{hash}?s={size}&d={default}&r={rating}'.format( 10 url=url, hash=hash, size=size, default=default, rating=rating 11 ) 12 return avatar
丰富用户资料字段
为WebUser新增三个字段,以丰富用户资料
1 nickname = db.Column(db.String(64)) 2 about_me = db.Column(db.Text()) 3 avatar_hash = db.Column(db.String(32))
添加路由函数
添加展示用户profile的函数,这里对使用GitHub登陆的用户做了判断,优先展示GitHub的头像和用户名
1@main.route('/user/<username>', methods=['GET', 'POST']) 2@login_required 3def user(username): 4 user = WebUser.query.filter_by(username=username).first() 5 if user is None: 6 abort(404) 7 if 'userid' in session: 8 thirduser = ThirdOAuth.query.filter_by(user_id=session['userid']).first() 9 if thirduser: 10 response = github.get('user', access_token=thirduser.oauth_access_token) 11 avatar = response['avatar_url'] 12 gituser = response['login'] 13 return render_template('user.html', user=user, avatar=avatar, gituser=gituser) 14 return render_template('user.html', user=user, avatar=None, gituser=None)
编辑用户资料
定义表单,包括username,nickname和about_me,用于提供给用户做修改
1class EditProfileForm(FlaskForm): 2 username = StringField('Username', validators=[Length(0, 64)]) 3 nickname = StringField('Nickname', validators=[Length(0, 64)]) 4 about_me = TextAreaField('About me') 5 submit = SubmitField('Submit')
然后在路由函数中判断,如果username存在且不是当前用户时,不能修改username,如果修改的username是唯一,那么更新数据库。
1@main.route('/edit-profile', methods=['GET', 'POST']) 2@login_required 3def edit_profile(): 4 form = EditProfileForm() 5 if form.validate_on_submit(): 6 username = form.username.data 7 if WebUser.query.filter_by(username=username).first() and username != current_user.username: 8 flash('This username has been used') 9 return redirect(url_for('main.edit_profile')) 10 current_user.username = form.username.data 11 current_user.nickname = form.nickname.data 12 current_user.about_me = form.about_me.data 13 db.session.add(current_user) 14 db.session.commit() 15 flash('Your profile has been updated') 16 return redirect(url_for('.user', username=current_user.username)) 17 form.username.data = current_user.username 18 form.nickname.data = current_user.nickname 19 form.about_me.data = current_user.about_me 20 return render_template('edit_profile.html', form=form)
页面演示
我将完整的代码上传到GitHub上了,有兴趣的同学可以戳这里:
https://github.com/zhouwei713/flask-webauth
另外还配置了一个演示web,地址为:
同时还创建了几个测试用户,可以来体验
普通用户1: user1@luobo.com/test1
普通用户2: user2@luobo.com/test2
管理员用户1: admin1@luobo.com/admin1
管理员用户2: admin2@luobo.com/admin2
也可以点击“阅读原文”进入
感兴趣的同学可以来玩玩