使用Flask-Login注册登录(2)

使用Flask-Login注册登录(2)

使用Flask-Login注册登录

登录可见

为了确保只有登录的用户才能访问页面,在Flask CSRF保护中,我们也自己实现了一个装饰器.

# limit.py
from functools import wraps
from flask import redirect,url_for,session

def limit_session(func):
   @wraps(func)
   def wrapper(*args,**kwargs):
       username = session.get('username',None)
       if username:
           return func(*args,**kwargs)
       else:
           session.clear()
           return redirect(url_for('login'))
   return wrapper

并把它应用到了视图中

class SaveMoneyView(views.MethodView):
   decorators = [limit_session]
   pass

同样的,Flask-Login也实现了相同的功能login_required

from  flask_login import login_required

# 登录可见的页面
@app.route('/personal/', methods=['GET', 'POST'])
@login_required
def personal():
   pass

同样可以查看源码,检查这一过程.

def login_required(func):
   @wraps(func)
   def decorated_view(*args, **kwargs):
       if request.method in EXEMPT_METHODS: # 通常该请求是获取服务器支持的HTTP请求方法
           return func(*args, **kwargs)
       elif current_app.config.get('LOGIN_DISABLED'): # 如果LOGIN_DISABLED 为True,则忽略验证
           return func(*args, **kwargs)
       elif not current_user.is_authenticated:  # 判断用户是否登录
           """
           没有登录的情况下:
           1.如果注册了 LoginManager.unauthorized_handler 则这个时候调用这个函数 
            2. 向用户提示 LoginManager.login_message信息 
            3.有 login_view的情况下,跳转到login_view,没有则返回abort(401)
            """

            return current_app.login_manager.unauthorized() 
        return func(*args, **kwargs)
    return decorated_view

以上涉及到了unauthorized()方法,这里可以简单看下

    def unauthorized(self):
        # current_app._get_current_object() 指向了app本身
        # 查看 flask上下文相关
        user_unauthorized.send(current_app._get_current_object())

        if self.unauthorized_callback:
            return self.unauthorized_callback()

        if request.blueprint in self.blueprint_login_views:
            login_view = self.blueprint_login_views[request.blueprint]
        else:
            login_view = self.login_view

        if not login_view:
            abort(401)

        if self.login_message:
            if self.localize_callback is not None:
                flash(self.localize_callback(self.login_message),
                      category=self.login_message_category)
            else:
                flash(self.login_message, category=self.login_message_category)

        config = current_app.config
        if config.get('USE_SESSION_FOR_NEXT', USE_SESSION_FOR_NEXT):
            login_url = expand_login_view(login_view)
            session['_id'] = self._session_identifier_generator()
            session['next'] = make_next_param(login_url, request.url)
            redirect_url = make_login_url(login_view)
        else:
            redirect_url = make_login_url(login_view, next_url=request.url)

        return redirect(redirect_url)
退出登录

对于退出登录,只需要调用logout_user即可.

from flask_login import logout_user
# 登出
@app.route('/logout/')
@login_required
def logout():
   logout_user()
   return 'logout'

源码

def _get_user():
   if has_request_context() and not hasattr(_request_ctx_stack.top, 'user'):
       current_app.login_manager._load_user()

   return getattr(_request_ctx_stack.top, 'user'None)

def logout_user():
   '''
   Logs a user out. (You do not need to pass the actual user.) This will
   also clean up the remember me cookie if it exists.
   '''


   user = _get_user()

   if '_user_id' in session:
       session.pop('_user_id')

   if '_fresh' in session:
       session.pop('_fresh')

   if '_id' in session:
       session.pop('_id')

   cookie_name = current_app.config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
   if cookie_name in request.cookies:
       session['_remember'] = 'clear'
       if '_remember_seconds' in session:
           session.pop('_remember_seconds')

   user_logged_out.send(current_app._get_current_object(), user=user)

   current_app.login_manager._update_request_context_with_user()
   return True

从堆栈结构中找到session中的user,实现登出操作.

自定义未授权访问的处理方法

@login_required装饰器对于未登录用户访问的默认处理是重定向到登录视图,如果我们不想它这么做的话,可以自定义处理方法:

@login_manager.unauthorized_handler
def unauthorized_handler():
   return 'Unauthorized'

这个@login_manager.unauthorized_handler装饰器所修饰的方法就会代替@login_required装饰器的默认处理方法.

有了上面的代码,当未登录用户访问index视图时,页面就会直接返回Unauthorized信息.

REmember Me

在登录视图中,调用login_user()方法,传入remeber=True,就可以实现记住我功能

login_user(curr_user, remember=True)

Flask-Login是通过在Cookie实现的,它会在Cookie中添加一个remember_token字段来记住之前登录的用户信息,所以禁用Cookie的话,该功能将无法工作.

fresh

当用户登录时,他们的会话会被标记为fresh(login_user()操作),即在session中设置_fresh=True.如果使用rember mecookie从新登录,会话被标记成not fresh.

在某些情况下,会强制要求用户登录一次,比如修改登录密码,这就需要fresh_login_required.

fresh_login_required除了可以验证用户登录,也将确保他们的登录是fresh,如果不是fresh,它会重新将用户准到输入验证条件的页面,这主要是为了确保用户修改个人信息的敏感操作.

def fresh_login_required(func):

   @wraps(func)
   def decorated_view(*args, **kwargs):
       if request.method in EXEMPT_METHODS:
           return func(*args, **kwargs)
       elif current_app.config.get('LOGIN_DISABLED'):
           return func(*args, **kwargs)
       elif not current_user.is_authenticated:
           return current_app.login_manager.unauthorized()
       elif not login_fresh():
           return current_app.login_manager.needs_refresh()
       return func(*args, **kwargs)
   return decorated_view
# confirm_login 将会话重新标记为 fresh
def confirm_login():
   session['_fresh'] = True
   session['_id'] = current_app.login_manager._session_identifier_generator() 
   user_login_confirmed.send(current_app._get_current_object())
from flask_login import fresh_login_required

@app.route('/login/')
@fresh_login_required
def home():
    return 'Logged in as: %s' % current_user.get_id()
Cookie设置

可以在config中设置cookie信息

REMEMBER_COOKIE_NAME 存储remember me信息的cookie名称,默认为remember_token
REMEMBER_COOKIE_DURATION cookie过期时长,默认365天
REMEMBER_COOKIE_DOMAIN 默认为None,比如设置为.example.com将会允许所有子域名
REMEMBER_COOKIE_PATH 限制remember me到一个路径,默认为/
REMEMBER_COOKIE_SECURE 限制remember mehttps
REMEMBER_COOKIE_HTTPONLY 限制remember me到客户端脚本访问
REMEMBER_COOKIE_REFRESH_EACH_REQUEST Ture:cookie每次刷新会延长生命周期
session保护

Flask-Login自动启用会话保护功能.对于每个请求,它会验证用户标识,这个标识是由客户端IP地址和”User Agent”的值经SHA512编码而来.在用户登录成功时,Flask-Login就会将这个值保存起来以便后续检查.默认的会话保护模式是basic,为了加强安全性,你可以启用强会话保护模式.方法是配置LoginManager实例对象中的session_protection属性:

login_manager.session_protection = "strong"

strong模式下,一旦用户标识检查失败,便会清空所用Session内容,并且”Remember Me”也失效。而basic模式下,只是将登录标为非Fresh登录。你还可以将login_manager.session_protection置为”None”来取消会话保护。

request_loader:禁用APIsession cookie

在对API进行认证时,我们可能不会用到cookie.为此,可以使用一个自定义的session接口,该接口根据在请求中设计的标志跳过保存session

from flask import g
from flask.sessions import SecureCookieSessionInterface
from flask_login import user_loaded_from_header

class CustomSessionInterface(SecureCookieSessionInterface):
   """防止 API 请求创建 session。"""
   def save_session(self, *args, **kwargs):
       if g.get('login_via_header'):
           return
       return super(CustomSessionInterface, self).save_session(*args,
                                                               **kwargs)

app.session_interface = CustomSessionInterface()

@user_loaded_from_header.connect
def user_loaded_from_header(self, user=None):
   g.login_via_header = True

这可以放置在用户使用request_loader进行认证时设置Flask Session

current_user

Flask-Login中可以直接使用current_user访问已经登录的用户,current_user可以在每个模板中世界使用.

{% if current_user.is_authenticated %}
Hi {{ current_user.name }}!
{% endif %}

3.实例

1.保存cookiememcache中,并设置超时时间

# app.py
from flask import Flask, request, abort, redirect, url_for, render_template, session
from flask_login import LoginManager, login_user, UserMixin, login_required, logout_user
import config
from urllib.parse import urlparse, urljoin
from flask_session import Session as Fsession
from datetime import timedelta

app = Flask(__name__)
app.config.from_object(config)
login_manager = LoginManager()
Fsession(app)

login_manager.init_app(app)
# 指定登录的URL
login_manager.login_view = 'login'

# 创建ORM映射
# 用户记录表
users = [
   {'username''Tom''password''111111'},
   {'username''Michael''password''123456'}
]


# 通过用户名,获取用户记录,如果不存在,返回None
def query_user(username):
   for user in users:
       if user['username'] == username:
           return user
       else:
           return None

class User(UserMixin):
   pass

# 如果用户名存在,就构造一个用户类对象,并使用用户名作为ID,如果不存在就返回None
# 回调函数
@login_manager.user_loader
def load_user(username):
   if query_user(username) is not None:
       curr_user = User()
       curr_user.id = username
       return curr_user
   else:
       return None

def is_safe_url(target):
   ref_url = urlparse(request.host_url)
   test_url = urlparse(urljoin(request.host_url, target))
   return test_url.scheme in ('http''https'and ref_url.netloc == test_url.netloc

@app.route('/login/', methods=['GET', 'POST'])
def login():
   # 假设通过表单验证
   # 假设通过数据库验证
   if request.method == 'POST':
       username = request.form.get('username')
       password = request.form.get('password')
       # 验证表单,数据库
       user = query_user(username)
       if user and password == user['password']:
           # curr_user 是 User类的一个实例
           curr_user = User()
           curr_user.id = username
           # 通过 Flask-login的login_user来登录账户
           login_user(curr_user, remember=True, duration=timedelta(days=60))
           nextD = request.args.get('next')
           print(nextD)
           # is_safe_url 用来检查url是否可以安全的重定向
           # 避免重定向攻击
           # if not is_safe_url(nextD):
           #     return abort(404)
           # return redirect(next or url_for('index'))
           return redirect(url_for('index'))
   else:
       return  render_template('login.html')

@app.route('/')
def index():
   return 'Hello World!'

# 登录可见的页面
@app.route('/personal/', methods=['GET', 'POST'])
@login_required
def personal():
   pass

# 登出
@app.route('/logout/')
@login_required
def logout():
   print(session['_user_id'])
   logout_user()
   try:
       print(session['_user_id'])
   except Exception as e:
       print('session已经删除')
   return 'logout'

if __name__ == '__main__':
   app.run()
# config.py
#!/usr/bin/env python
# coding=utf-8
import os
from exts import client
from datetime import timedelta

DEBUG = True
TEMPLATES_AUTO_RELOAD = True
SECRET_KEY = os.urandom(24)

# 设置flask-session相关
SESSION_TYPE = 'memcached' # 指定类型
SESSION_MEMCACHED = client # 指定实例
SESSION_USE_SIGNER = True  # 设置加密
SESSION_KEY_PREFIX = 'session' # 指定值的前缀
PERMANENT_SESSION_LIFETIME = timedelta(days=0)
# exts.py
from pymemcache.client import Client
client = Client(('192.168.0.101'11211), allow_unicode_keys=True, encoding='utf8')
<!-- login.html --->
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
</head>
<body>
{% if current_user.is_authenticated %}
   Hi {{ current_user.name }}
{% endif %}
<form action="{{ url_for('login') }}" method="post">
   <label for="">username:</label><input type="text" name="username">
   <label for="">password:</label><input type="password" name="password">
   <input type="checkbox" value="remember me" name="remember">
   <input type="submit" name="submit">
</form>
</body>
</html>

– END –


原文始发于微信公众号(Flask学习笔记):使用Flask-Login注册登录(2)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/36416.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!