从头搭建一个flask鉴权系统之登陆

简介: “ 从今天开始,准备从头开始搭建一个基于flask的鉴权系统,一点一滴,积累于生活”

01.知识树


本文涉及到如下知识点

1. flask-login的简单使用

2. 本地鉴权实践

3. GitHub鉴权登陆实践,flask-github使用

4. 可扩展的表结构设计思路


02.表结构设计


我们首先设计一个User用户表,里面的字段可以包括username,password,email等用户信息,大致如下


username password email
user1 p1 user1@gmail.com
user2 p2 user2@gmail.com
user3 p3 user3@gmail.com

因为我们还会涉及到第三方登陆,那么为了后面便于扩展,再设计一张表,就命名为ThirdAuth,里面可以包括user_id,与user表关联,oauth_name,oauth_access_token等字段


user_id oauth_name oauth_access_token
user-id1 auth1 token1
user-id2 auth2 token2
user-id3 auth3 token3


这样,oauth_name字段可以用来存储第三方来源,例如github,以此来区别不同的第三方登陆用户。

到此,一个简单的表结构就设计好了。


03.OAuth鉴权


简单来说,为一个网站添加第三方登录指的是提供通过其他第三方平台账号登入当前网站的功能。比如,使用QQ、微信、新浪微博账号登录。对于某些网站,甚至可以仅提供社交账号登录的选项,这样网站本身就不需要管理用户账户等相关信息。对用户来说,使用第三方登录可以省去注册的步骤,更加方便和快捷。这里,我就是使用GitHub的OAuth认证来进行鉴权登陆。

这里首先需要在自己的GitHub上创建一个OAuth程序,非常简单,访问这个地址:https://github.com/settings/applications/new,按照要求填写即可。


image.png

其中的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()
        if users is None:
            users = WebUser(email='admin@123.com', username='admin', user_id=time.time())
        users.password = '123456'
        db.session.add(users)
        db.session.commit()
    @property
    def password(self):
        raise AttributeError('password is not readable attribute')
    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)
    def verify_password(self, password):
        return check_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('Keep me logged in')
    submit = SubmitField('Log In')


3. 定义登陆登出函数

当表单正确提交时,如果用户名和密码匹配,则提示登陆成功,并跳转页面,否则提示登陆失败。

因为是使用flask-login扩展,所以登陆直接调用login_user()即可。

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = WebUser.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('Invalid username or password!')
    return render_template('auth/login.html', form=form)

对于登出,同样简单,注意需要用login_required装饰器保证只有已经登陆的用户才能调用该函数。

@auth.route('/logout')
@login_required
def logout():
    flash('You have logged out!')
    return redirect(url_for('main.index'))


4. web模板

创建一个base.html基础模板(继承自flask-bootstrap模板),后面其他页面都继承自该模板,这样可以保证所有的页面风格统一,也可以减少代码量。

{% extends "bootstrap/base.html" %} 
{% block title %}Flasky{% endblock %} 
{% block navbar %}
<div class="navbar navbar-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">Toggle navigation</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-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if current_user.is_authenticated %}
                <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>
                {% else %}
                <li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
                {% endif %}
            </ul>
        </div>
    </div>
</div>
{% endblock %}
{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
</div>
{% endblock %}


5. 登陆页面

登陆页面继承自base.html模板,并使用wtf快速渲染表单

{% extends "base.html" %}
{% import  "bootstrap/wtf.html" as wtf %}
{% block title %}Login{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
</div>
{% endblock %}

最后的登陆页面为


image.png


6. 初始化数据库

使用flask-script扩展,定义runserver和shell两个命令行命令,shell用于数据库等调测操作,runserver用于启动服务。

from app import create_app, db
from flask_script import Manager, Shell, Server
from app.models import WebUser
app = create_app('testing')
manager = Manager(app)
def make_shell_context():
    return dict(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. 登陆测试

在输入框分别键入admin@163.com和123456,并点击登陆,发现可以正常登陆,效果如下


image.png

其中index页面代码为

{% extends "base.html" %}
{% import  "bootstrap/wtf.html" as wtf %}
{% block title %}Login{% endblock %}
{% block page_content %}
<div 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 }}" />
    </div>
{% else %}
    Your are not login yet
{% endif %}
</div>
{% endblock %}


05. GitHub鉴权


1. 创建表结构

类似的,定义需要的字段即可

class ThirdOAuth(db.Model):
    __tablename__ = 'thirdoauth'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.String(64), unique=True, index=True)
    oauth_name = db.Column(db.String(128))
    oauth_id = db.Column(db.String(128), unique=True, index=True)
    oauth_access_token = db.Column(db.String(128), unique=True, index=True)
    oauth_expires = db.Column(db.String(64), unique=True, index=True)


2. 发送授权请求

这一步,flask-github已经为我们封装好了,直接调用即可

@auth.route('/githublogin', methods=['GET', 'POST'])
def githublogin():
    return github.authorize(scope='repo')

这里需要说明,该调用需要用到我们前面获得的客户端ID和密钥,我这里把相关信息写到了一个配置文件中,并在初始化flask app时加载

配置文件

class Config:
    SECRET_KEY = "hardtoguess"
    GITHUB_CLIENT_ID = 'cf1AA35ef11d20bcdXXX'
    GITHUB_CLIENT_SECRET = 'ba7c8c8SSe9cd574eb3da1b5e704d11d35aXXXb8'

初始化app

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    db.init_app(app)
    cors.init_app(app, supports_credentials=True)
    login_manager.init_app(app)
    bootstrap.init_app(app)
    github.init_app(app)
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    from .api_1_0 import api_1_0 as api_blueprint
    app.register_blueprint(api_blueprint)
    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')
    return app


3. 获取access令牌

当用户同意授权或拒绝授权后,GitHub会将用户重定向到我们设置的callback URL,我们需要创建一个视图函数来处理回调请求。如果用户同意授权,GitHub会在重定向的请求中加入code参数,一个临时生成的值,用于程序再次发起请求交换access token。程序这时需要向请求访问令牌URL(即https://github.com/login/oauth/access_token)发起一个POST请求,附带客户端ID、客户端密钥、code。请求成功后的的响应会包含访问令牌(Access Token)。


很幸运,上面的一系列工作flask-github会在背后替我们完成。我们只需要创建一个视图函数,定义正确的URL规则(这里的URL规则需要和GitHub上填写的Callback URL匹配),并为其附加一个github.authorized_handler装饰器。另外,这个函数要接受一个access_token参数,GitHub-Flask会在授权请求结束后通过这个参数传入访问令牌。

同时判断,该用户是否存在于数据库中,并更新相关字段。

@auth.route('/callback/github')
@github.authorized_handler
def authorized(access_token):
    if access_token is None:
        flash('Login Failed!')
        return redirect(url_for('main.index'))
    response = github.get('user', access_token=access_token)
    username = response['login']
    u_id = response['id']
    email = response['email']
    avatar = response['avatar_url']
    user = WebUser.query.filter_by(username=username).first()
    if user is None:
        user = WebUser(username=username, user_id=time.time())
        db.session.add(user)
        db.session.commit()
        thirduser = ThirdOAuth(user_id=WebUser.query.filter_by(username=username).first().user_id,
                               oauth_name='github', oauth_access_token=access_token,
                               oauth_id=u_id)
        db.session.add(thirduser)
        db.session.commit()
        login_user(user)
        user.email = email
        db.session.add(user)
        db.session.commit()
        session['userid'] = user.user_id
        return render_template('index.html', avatar=avatar)
    else:
        thirduser = ThirdOAuth.query.filter_by(user_id=user.user_id).first()
        thirduser.oauth_access_token = access_token
        db.session.add(thirduser)
        db.session.commit()
        user.email = email
        db.session.add(user)
        db.session.commit()
        login_user(user)
        session['userid'] = user.user_id
        return render_template('index.html', avatar=avatar)


更多的GitHub开发文档资料可以查看:

https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/

更多flask-github资料可以查看:

https://github-flask.readthedocs.io/en/latest/


4. 更新登陆页面

更新登陆页面,增加一个以GitHub登陆的按钮

<div class="col-md-12">
    <a class="btn btn-primary" href="{{ url_for('auth.githublogin') }}">Login with GitHub</a>
</div>

现在的登陆页面为


image.png

更新index路由函数,增加以GitHub登陆时的头像

@main.route('/', methods=['GET', 'POST'])
def index():
    # print(session)
    if current_user.is_authenticated:
        if 'userid' in session:
            user = ThirdOAuth.query.filter_by(user_id=session['userid']).first()
            if user:
                response = github.get('user', access_token=user.oauth_access_token)
                avatar = response['avatar_url']
                username = response['login']
                return render_template('index.html', username=username, avatar=avatar)
    return render_template('index.html')

又因为在callback函数中增加了session.userid字段,所以在logout时,把该字段手动删除

@auth.route('/logout')
@login_required
def logout():
    logout_user()
    if 'userid' in session:
        session.pop('userid')
    flash('You have logged out!')
    return redirect(url_for('main.index'))


5. 测试GitHub登陆

登陆成功后,如下


image.png

至此,登陆功能完成


完整代码:

https://github.com/zhouwei713/flask-webauth

相关文章
|
3月前
|
安全 数据安全/隐私保护 Python
基于Flask框架实现一个简易后台用户登录系统
基于Flask框架实现一个简易后台用户登录系统
|
3月前
|
人工智能 安全 数据安全/隐私保护
基于Flask框架实现一个简易后台用户登录系统
基于Flask框架实现一个简易后台用户登录系统
|
4月前
|
数据采集 自然语言处理 数据可视化
优秀python系统案例】基于python Flask的电影票房数据爬取与可视化系统的设计与实现
本文介绍了一个基于Python Flask框架开发的电影票房数据爬取与可视化系统,该系统利用网络爬虫技术从豆瓣电影网站抓取数据,通过Python进行数据处理和分析,并采用ECharts等库实现数据的可视化展示,为电影行业从业者提供决策支持。
266 2
优秀python系统案例】基于python Flask的电影票房数据爬取与可视化系统的设计与实现
|
4月前
|
数据采集 数据可视化 关系型数据库
【优秀python web设计】基于Python flask的猫眼电影可视化系统,可视化用echart,前端Layui,数据库用MySQL,包括爬虫
本文介绍了一个基于Python Flask框架、MySQL数据库和Layui前端框架的猫眼电影数据采集分析与可视化系统,该系统通过爬虫技术采集电影数据,利用数据分析库进行处理,并使用Echart进行数据的可视化展示,以提供全面、准确的电影市场分析结果。
139 4
|
4月前
|
机器学习/深度学习 数据采集 数据可视化
【优秀python系统毕设】基于Python flask的气象数据可视化系统设计与实现,有LSTM算法预测气温
本文介绍了一个基于Python Flask框架开发的气象数据可视化系统,该系统集成了数据获取、处理、存储、LSTM算法气温预测以及多种数据可视化功能,旨在提高气象数据的利用价值并推动气象领域的发展。
173 1
|
4月前
|
机器学习/深度学习 数据采集 存储
基于Python+flask+echarts的气象数据采集与分析系统,可实现lstm算法进行预测
本文介绍了一个基于Python、Flask和Echarts的气象数据采集与分析系统,该系统集成了LSTM算法进行数据预测,并提供了实时数据监测、历史数据查询、数据可视化以及用户权限管理等功能。
105 0
|
4月前
|
机器学习/深度学习 算法 数据可视化
基于Python flask的豆瓣电影数据分析可视化系统,功能多,LSTM算法+注意力机制实现情感分析,准确率高达85%
本文介绍了一个基于Python Flask框架的豆瓣电影数据分析可视化系统,该系统集成了LSTM算法和注意力机制进行情感分析,准确率高达85%,提供了多样化的数据分析和情感识别功能,旨在帮助用户深入理解电影市场和观众喜好。
138 0
|
19天前
|
开发框架 前端开发 JavaScript
利用Python和Flask构建轻量级Web应用的实战指南
利用Python和Flask构建轻量级Web应用的实战指南
57 2
|
29天前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
32 2
|
1月前
|
JSON API 数据格式
构建RESTful APIs:使用Python和Flask
构建RESTful APIs:使用Python和Flask
29 1