“
从今天开始,准备从头开始搭建一个基于flask的鉴权系统,一点一滴,积累于生活”
从登陆开始
01.
知识树
本文涉及到如下知识点
1. flask-login的简单使用
2. 本地鉴权实践
3. GitHub鉴权登陆实践,flask-github使用
4. 可扩展的表结构设计思路
02.
表结构设计
我们首先设计一个User用户表,里面的字段可以包括username,password,email等用户信息,大致如下[email protected]
因为我们还会涉及到第三方登陆,那么为了后面便于扩展,再设计一张表,就命名为ThirdAuth,里面可以包括user_id,与user表关联,oauth_name,oauth_access_token等字段user_idoauth_nameoauth_access_tokenuser-id1auth1token1
user-id2auth2token2
user-id3auth3token3
这样,oauth_name字段可以用来存储第三方来源,例如github,以此来区别不同的第三方登陆用户。
到此,一个简单的表结构就设计好了。
03.OAuth鉴权
简单来说,为一个网站添加第三方登录指的是提供通过其他第三方平台账号登入当前网站的功能。比如,使用QQ、微信、新浪微博账号登录。对于某些网站,甚至可以仅提供社交账号登录的选项,这样网站本身就不需要管理用户账户等相关信息。对用户来说,使用第三方登录可以省去注册的步骤,更加方便和快捷。这里,我就是使用GitHub的OAuth认证来进行鉴权登陆。
这里首先需要在自己的GitHub上创建一个OAuth程序,非常简单,访问这个地址:https://github.com/settings/applications/new,按照要求填写即可。
其中的callback需要填写一个回调函数,具体后面再说。
创建好这个OAuth程序后,我们就会获得Client ID(客户端ID)和Client Secret(客户端密钥),在后面调用Github的API时使用。
04. 本地鉴权
1. 创建表结构
根据刚才的表结构设计,对于本地鉴权,可以在models.py文件中创建一个WebUser类,定义对应的数据库字段。
对于password,不建议直接在数据库中存储明文,所以这里使用了werkzeug库来做hash转换。
同时WebUser类还继承自flask-login的UserMixin类,该类实现了关键的用于检测用户状态的方法:
is_authenticated,如果用户已经登陆返回True,否则返回False
is_active,如果用户允许登陆,返回True,否则返回Flase
is_anonymous,对普通用户必须返回False
get_id,必须返回用户的唯一标识
后面主要使用到了
is_authenticated方法。
而init_user是用来初始化第一个用户的,password等几个方法分别是用来检测密码是否正确的。
class
WebUser
(UserMixin,db.Model):
__tablename__=
"webuser"
id=db.Column(db.Integer,primary_key=
True)
user_id=db.Column(db.String(
64),unique=
True,index=
True)
email=db.Column(db.String(
64),unique=
True,index=
True)
username=db.Column(db.String(
64),unique=
True,index=
True)
password_hash=db.Column(db.String(
128))
@staticmethod
def
init_user
():
users=WebUser.query.filter_by(username=
"admin").first()
ifusers
is
None:
users=WebUser(email=
"[email protected]",username=
"admin",user_id=time.time())
users.password=
"123456"
db.session.add(users)
db.session.commit()
@property
def
password
(self):
raiseAttributeError(
"passwordisnotreadableattribute")
@password.setter
def
password
(self,password):
self.password_hash=generate_password_hash(password)
def
verify_password
(self,password):
returncheck_password_hash(self.password_hash,password)
2. 定义登陆表单
登陆表单比较简单,两个输入框,分别为用户名和密码,一个check box,用来选择是否保持登陆,外加一个提交按钮class
LoginForm
(FlaskForm):
email=StringField(
"Email",validators=[DataRequired(),Length(
1,
64),Email()])
password=PasswordField(
"Password",validators=[DataRequired()])
remember_me=BooleanField(
"Keepmeloggedin")
submit=SubmitField(
"LogIn")
3. 定义登陆登出函数
当表单正确提交时,如果用户名和密码匹配,则提示登陆成功,并跳转页面,否则提示登陆失败。
因为是使用flask-login扩展,所以登陆直接调用login_user()即可。
@auth.route("/login",methods=["GET","POST"])
def
login
():
form=LoginForm()
ifform.validate_on_submit():
user=WebUser.query.filter_by(email=form.email.data).first()
ifuser
is
not
None
anduser.verify_password(form.password.data):
login_user(user,form.remember_me.data)
returnredirect(request.args.get(
"next")
orurl_for(
"main.index"))
flash(
"Invalidusernameorpassword!")
returnrender_template(
"auth/login.html",form=form)
对于登出,同样简单,注意需要用login_required装饰器保证只有已经登陆的用户才能调用该函数。@auth.route("/logout")
@login_required
def
logout
():
flash(
"Youhaveloggedout!")
returnredirect(url_for(
"main.index"))
4. web模板
创建一个base.html基础模板(继承自flask-bootstrap模板),后面其他页面都继承自该模板,这样可以保证所有的页面风格统一,也可以减少代码量。
{%extends"bootstrap/base.html"%}
{%blocktitle%}Flasky{%endblock%}
{%blocknavbar%}
<
div
class=
"navbarnavbar-inverse"
role=
"navigation">
<
div
class=
"container">
<
div
class=
"navbar-header">
<
button
type=
"button"
class=
"navbar-toggle"
data-toggle=
"collapse"
data-target=
".navbar-collapse">
<
span
class=
"sr-only">Togglenavigation
span>
<
span
class=
"icon-bar">
span>
<
span
class=
"icon-bar">
span>
<
span
class=
"icon-bar">
span>
button>
<
a
class=
"navbar-brand"
href=
"/">WebAuth
a>
div>
<
div
class=
"navbar-collapsecollapse">
<
ul
class=
"navnavbar-nav">
<
li><
a
href=
"/">Home
a>
li>
ul>
<
ul
class=
"navnavbar-navnavbar-right">
{%ifcurrent_user.is_authenticated%}
<
li><
a
href=
"{{url_for("auth.logout")}}">SignOut
a>
li>
{%else%}
<
li><
a
href=
"{{url_for("auth.login")}}">SignIn
a>
li>
{%endif%}
ul>
div>
div>
div>
{%endblock%}
{%blockcontent%}
<
div
class=
"container">
{%blockpage_content%}{%endblock%}
div>
{%endblock%}
5. 登陆页面
登陆页面继承自base.html模板,并使用wtf快速渲染表单{%extends"base.html"%}
{%import"bootstrap/wtf.html"aswtf%}
{%blocktitle%}Login{%endblock%}
{%blockpage_content%}
<
div
class=
"page-header">
<
h1>Login
h1>
div>
<
div
class=
"col-md-4">
{{wtf.quick_form(form)}}
div>
{%endblock%}
最后的登陆页面为
6. 初始化数据库
使用flask-script扩展,定义runserver和shell两个命令行命令,shell用于数据库等调测操作,runserver用于启动服务。
fromapp
importcreate_app,db
fromflask_script
importManager,Shell,Server
fromapp.models
importWebUser
app=create_app(
"testing")
manager=Manager(app)
def
make_shell_context
():
returndict(app=app,db=db,WebUser=WebUser)
manager.add_command(
"runserver",Server(use_debugger=
True,host=
"0.0.0.0",port=
"9982"))
manager.add_command(
"shell",Shell(make_context=make_shell_context))
if__name__==
"__main__":
manager.run(default_command=
"runserver")
在命令行输入python manage.py shell,进入调测shell,然后输入db.create_all()和WebUser.init_user(),分别创建表并插入原始用户。
7. 登陆测试
在输入框分别键入[email protected]和123456,并点击登陆,发现可以正常登陆,效果如下
其中index页面代码为{%extends
"base.html"%}
{%
import
"bootstrap/wtf.html"
aswtf%}
{%blocktitle%}Login{%endblock%}
{%blockpage_content%}
class="
container">
{%
for
message
in
get_flashed_messages
()%}
<
div
class="
alert
alert-
warning">
<
button
type="
button"
class="
close"
data-
dismiss="
alert">&
times;
button>
{{
message}}
div>
{%
endfor%}
div>
<
div
class="
page-
header">
<
h1>
Home
h1>
div>
<
div
class="
col-
md-4">
这是首页
div>
<
div
class="
col-
md-12">
{%
if
current_user.
is_authenticated%}
{{
current_user.
username}}
{{
name}}
<
div>
<
img
style="-
webkit-
user-
select:none;
"src="{{avatar}}
"/>
{%else%}
Yourarenotloginyet
{%endif%}