flask-login(0.1.1)源码

源码链接: https://github.com/maxcountryman/flask-login/blob/0.1.1/flaskext/login.py
python版本:2.7.12
系统: ubuntu16.04

(1) 从_request_ctx_stack的top中获取user属性

def _get_user():
    return getattr(_request_ctx_stack.top, "user", None)

_request_ctx_stack是从flask中导入的,本质是werkzeug.local中LocalProxy
问题: 既然可以获取user, 哪什么时候把user存入LocaalProxy栈中?

(2) 对值(用户名/id等)进行加密

def _cookie_digest(payload, key=None):
    if key is None:
        key = current_app.config['SECRET_KEY']
    payload = payload.encode('utf-8')
    mac = hmac.new(key, payload, sha1)
    return mac.hexdigest()

需要加密的值(payload)和secret key,采用sha1加密方式

(3) 利用_cookie_digest()函数生成cookie

def encode_cookie(payload):
    return '%s|%s' % (payload, _cookie_digest(payload))

将payload和加密payload产生的值组合一起产生一个新值,可以保存在cookie之中.客户端将cookie传回服务端,程序取出payload,采用同样的方式(_cookie_digest()函数)加密,生成一个值,并与传回来的加密值进行校验,判断是不是同一个用户

(4) 解析cookie

def decode_cookie(cookie):
    try:
        payload, digest = cookie.rsplit(u'|', 1)
        digest = digest.encode("ascii")
    except ValueError:
        return None
    if _cookie_digest(payload, key = 'fsdfaiew') == digest:
        return payload
    else:
        return None

问题: 第3个和第4个函数是不是可以自定义?

(5) 提取将要访问url的参数部分(去掉url中协议:ip:port部分和锚点部分)

def make_next_param(login, current):
    '''
    :param login: 要跳转的url
    :param current: 要分解的url
    :return '/home/fg;param=123?query=1' 或者current
    '''
    login_scheme, login_netloc = urlparse(login)[:2]
    current_scheme, current_netloc = urlparse(current)[:2]
    # 协议和ip(包括port)均相同 login协议\ip均不存在 login协议存在且相同ip不存在
    # login协议不存在且ip存在且相同

    # login协议要么不存在 要么存在且和current相同
    # login ip要么不存在 要么存在且和current相同
    if ((not login_scheme or login_scheme=current_scheme) and
        (not login_netloc or login_netloc=current_netloc)):
        parsed = urlparse(current)
        # 组合: 路径+参数+查询
        # http://www.baidu.com/home/fg;param=123?query=1
        # --> /home/fg;param=123?query=1
        return urlunparse(('', '', parsed[2], parsed[3], parsed[4], ''))
    return current

用urlparse.urlpasre可以将url拆分,用urlparse.urlunparse组合url
如: https://www.baidu.com/home/fg;params=123?name=chim&id=1#123
拆分成六部分:
1. scheme: https
2. netloc: www.baidu.com ip+port
3. path: /home/fg
4. params: params=123 不知道用途
5. query: name=chim&id=1
6. fragment: name 锚点 不作用于服务器

注意:在python3.5中用from urllib.parse import urlparse, urlunparse

(6) 生成将要访问的url

def login_url(login_view, next_url=None, next_field="next"):
    if login_view.startswith(('https://', 'http://')):
        base = login_view
    else:
        base = url_for(login_view)
    if next_url is None:
        return base
    # base为完整的url
    parts = list(urlparse(base))
    # parts[4]为url查询部分 如: 'name=chim&id=1'
    # url_decode是werkzeug.url中函数: 解析参数  -->md = MultiDict([('name', u'chim'),('id', u'1')])
    md = url_decode(parts[4])
    # 将'next'加入md, next为'path;params?query'
    md[next_field] = make_next_param(base, next_url)
    parts[4] = url_encode(md, sort=True)
    return urlunparse(parts)

提取将要访问url中的(param+query+flagment)组合部分,为它定义一个’next’名称,加入当前访问url的query部分

(7) 生成安全token

def make_secure_token(*args, **options):
    key = options.get('key')
    if key is None:
        key = current_app.config['SECRET_KEY']
    # 如果是unicode字符集,采用utf-8编码成字符串
    payload = "\0".join((
        s.encode("utf-8") if isinstance(s, unicode) else s) for s in args
    )
    # 生成token
    mac = hmac.new(key, payload, sha1)
    # 返回unicode字符串40位
    return mac.hexdigest().decode('utf-8')

采用sha1加密方式
注意: 此本版定义了此函数, 但并未使用

(8) 访问认证

def _create_identifier():
    # '客户端主机ip地址|user-agent头'
    # unicode()将字符串解码为unicode字符集
    base = unicode("%s|%s" % (request.remote_addr,
                              request.headers.get("User-Agent")), 'utf-8')

    hsh = md5()
    hsh.update(base.encode('utf-8'))
    # 二进制
    return hsh.digest()

将客户端主机ip地址和User-Agent头信息进行组合.用于确认用户再次访问时,访问地址和用户代理是否和前一次访

(9) 参数部分

COOKIE_NAME = 'remember_token'
COOKIE_DURATION = timedelta(days=365)
LOGIN_MESSAGE = u'please log in access this page'
REFRESH_MESSAGE = u'please reauthenticate to this page'

(10) LoginManager类 init()

class LoginManager(object):
    def __init__(self):
        # 匿名用户
        self.anonymous_user = AnonymousUser
        # url视图endpoint或者url,若未登录,重定向
        self.login_view = None
        # 登录消息
        self.login_message = LOGIN_MESSAGE
        # 重新认证视图
        self.refresh_view = None
        # 重新认证消息
        self.needs_refresh_message = REFRESH_MESSAGE
        # session保护等级basic(默认),strong, None
        self.session.protection = 'basic'
        # token处理函数,可以用token_loader(self)方法进行自定义
        self.token_callback = None
        # 用户处理函数(如重数据库中获取用户信息)
        # 可以用user_loader(self)方法进行自定义
        self.user_callback = None
        # 用户认证处理函数,可以用unauthorized_handler(self)方法进行自定义
        # 与user_loader(self)不同的是user_loader(self)是获取用户信息,
        # unauthorized_handler(self)是在获取用户信息之后,对用户身份的标识.
        # 如用户存在,标识为已认证用户;用户不存在,标识为匿名用户
        self.unauthorized_callback = None
        # 与self.user_callback()类似,只是它是对即将访问资源的处理
        # 如资源存在,如何处理,资源不存在如何处理
        $ 可以用needs_refresh_handler(self)方法进行自定义
        self.needs_refresh_callback = None

(11) 自定义获取用户信息方法

def user_loader(self, callback):
    self.user_callback = callback

返回获取用户处理的方法,可以当作装饰器使用

# 自定义用户获取方法
@lg.user_loader
def load_user(id):
    return User.query.filter_by(id=id).first()

(12) 自定义处理cookie中token方法

 def token_loader(self, callback):
        self.token_callback = callback

可以作为装饰器使用,目的在于获取cookie之中的用户信息

(13) 自定义用户未认证时的处理方法

def unauthorized_handler(self, callback):
    self.unauthorized_callback = callback

可以作为装饰器使用

(14) 自定义访问资源时的处理方法

def needs_refresh_handler(self, callback):
    self.needs_refresh_callback = callback

可以作为装饰器使用

(15) 初始化方法

def setup_app(self, app, add_context_processor=True):
    app.login_manager = self
    app.before_request(self._load_user)
    app.after_request(self._update_remember_cookie)
    if add_context_processor:
       app.context_processor(_user_context_processor)

初始化Flask()对象,可以清晰知道flask-login做了什么.主要包括:
1. 在请求处理之前,对用户进行认证
2. 在请求完成之后,设置cookie信息
3. 将_user_context_processor加入至上下文变量之中,也就是current_user
注意:flask-login后面版本改为了init_app(self)

(16) 认证处理

def unauthorized(self):
    # 发送信号 
    user_unauthorized.send(current_app._get_current_object())
    if self.unauthorized_callback:
        return self.unauthorized_callback()
    # 用户未认证且登录视图不存在时,401错误
    if not self.login_view:
        abort(401)
    if self.login_message:
        flash(self.login_message)
    # login_view是一个用户未认证要跳转的url(可能不完整), request.url是当前请求的url(完整)
    return redirect(login_url(self.login_view, request.url))

主要与状态码401(未登陆)的处理有关

(17) 资源获取处理

 def needs_refresh(self):
     user_needs_reflesh.send(current_app._get_current_object())
     if self.needs_refresh_callback:
         return self.needs_refresh_callback()
     if not self.refresh_view:
         abort(403)
     flash(self.needs_refresh_message)
     return redirect(login_url(self.refresh_view, request.url))

主要与状态码403(Forbidden)的处理有关

(18) 加载用户

    def _load_user(self):
        config = current_app.config
        # 获取session安全等级
        if config.get('SESSION_PROTECTION', self.session_protection):
            deleted = self._session_protection()
            # deleted为True,重新加载user
            if deleted:
                self.reload_user()
                return
        # 若设置了remember cookie, session未设置,将cookie里user的id移至session中
        cookie_name = config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
        if cookie_name:
            self._load_from_cookie(request.cookie[cookie_name])
        else:
            # 重新加载user
            self.reload_user()

加载用户的方式有两种:
1. 从session中获取,但session保护等级有限制
2. session加载失败,从cookie中获取
两者最终都会reload_user(self)(在当前app的栈中进行处理)

(19) session保护机制处理

 def _session_protection(self):
        # 从session中获取用户对象
        sess = session._get_current_object()
        # 访问认证  '访问地址|user-agent头'
        ident = _create_identifier()
        # 若'_id'不在sess中, 将其保存在其中,session的基础是访问的remote_addr和代理相同
        if '_id' not in sess:
            sess['_id'] = ident
        # sess中存在'_id',但和当前的'访问地址|user-agent'不同, 获取当前用户
        elif ident != sess['_id']:
            app = current_app._get_current_object()
            mode = app.config.get['SESSION_PROTECTION', self.session_protection]
            # 若session保护等级为basic(sess.permanent保存什么?), session继续使用(换了代理和网址也不要紧)
            if mode == 'basic' or sess.permanent:
                sess['_fresh'] = False
                # ? send有什么作用
                session_protected.send(app)
                return False
            # 若session保护等级为strong, 删除当前保存的session,不允许保存不同地址的session,换地址则更新session
            elif mode == 'strong':
                sess.clear()
                sess['remember'] = 'clear'
                session_protected.send(app)
                return True
        return False

返回True或者False,并在session中记录’_id’, ‘remember’, ‘_fresh’的状态
问题: 为什么要用session._get_current_object(),而不是之际存在flask.session中.在其他地方对这三个变量的取值是直接从flask.session中获取的?

(20) 重新加载用户

def reload_user(self):
    ctx = _request_ctx_stack.top
    user_id = session.get('user_id', None)
    # 若session中不存在user_id,设置用户为匿名用户
    if user_id is None:
        ctx.user = self.anonymous_user
    else:
        # 加载用户
        user = self.user_callback(user_id)
        if user is None:
            logout_user()
        else:
            ctx.user = user

重新加载用户, 将user放入上下文之中.如果是这样, ctx对_request_ctx_stack.top引用是地址的引用,ctx是别名

(21)从cookie中加载用户

def _load_from_cookie(self, cookie):
    # token_callback是什么?
    if self.token_callback:
        user = self.token_callback(cookie)
        # 用户存在
        if user is not None:
            session['user_id'] = user.get_id()
            session['_fresh'] = False
            _request_ctx_stack.top.user = user
        # 用户不存在,reload_user
        else:
            self.reload_user()
    else:
        # 解析cookie, 返回用户标识
        user_id = decode_cookie(cookie)
        if user_id is not None:
            session['user_id'] = user_id
            # '_fresh'含义是什么?
            session['_fresh'] = False
        self.reload_user()

从cookie中取出用户,用reload_user保存用户至上下文中

(22) 更新remember cookie

def _update_remember_cookie(self, response):
    # 在session中取出并删除remember的值
    operation = session.pop('remember', 'None')
    # remember me 操作(一次性的)
    if operation == 'set' and 'user_id' in session:
        self._set_cookie(response)
    elif operation == 'clear':
        self._clear_cookie(response)
    return response

(23) remember me 的set cookie操作

def _set_cookie(self, response):
    config = current_app.config
    cookie_name = config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
    duration = config.get('REMEMBER_COOKIE_DURATION', COOKIE_DURATION)
    # 主机信息
    domain = config.get('REMEMBER_COOKIE_DOMAIN', None)
    if self.token_callback:
        data = current_user.get_auth_token()
    else:
        data = encode_cookie(str(session['user_id']))
    expires = datetime.now() + duration
    response.set_cookie(cookie_name, expires=expires, domain=domain)

(24) remember me 的clear cookie操作

def _clear_cookie(self, response):
    config = current_app.config
    cookie_name = config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
    domin = config.get('REMEMBER_COOKIE_DOMAIN', None)
    response.delete_cookie(cookie_name, domain=domin)

(25) current_user定义

current_user = LocalProxy(lambda: _request_ctx_stack.top.user)

def _user_context_processor():
    return dict(current_user=_get_user())

问题: 为什么要用lambda?不用好像也可以

(26) 获取cookie的状态

def login_fresh():
    return session.get('_fresh', False)

_fresh–用户新登录,返回True

(27)用户登录

def login_user(user, remember=False, force=False):
    if (not force) and (not user.is_active()):
        return False
    user_id = user.get_id()
    session['user_id'] = user_id
    session['_fresh'] = True
    if remember:
        session['remember'] = 'set'
    app = current_app._get_current_object()
    current_app.login_manager.reload_user()
    user_logged_in.send(current_app._get_current_object(), user=_get_user())
    return True

问题: force的正确用法是什么?为什么要设置这个值?

(28)用户登出

def logout_user():
    if 'user_id' in session:
        del session['user_id']
    if '_fresh' in session:
        del session['_fresh']
    cookie_name = current_app.config.get['REMEMBER_COOKIE_NAME', COOKIE_NAME]
    # request.cookies为字典
    if cookie_name in request.cookies:
        # remember的值决定cookie的存在与否
        session['remember'] = 'clear'
    user = _get_user()
    if user and (not user.is_anonymous()):
        user_logged_out.send(current_app._get_current_object(), user=user)
    current_app.login_manager.reload_user()
    return True

(29) 用户认证后设置_fresh值,并发送信号

def comfirm_login():
    # 最新登录的用户,_fresh设置为True,用cookie加载,_fresh为False
    session['_fresh'] = True
    user_login_confirmed.send(current_app._get_current_object())

(30) 登录限制装饰器

def login_required(fn)
    @wraps(fn)
    def decorated_view(*args, **kwargs):
        # is_authenticated()在用户类中定义,默认返回True/False
        if not current_user.is_authenticated():
            # 重定向值login_view
            return current_app.login_manager.unauthorized()
        return fn(*args, **kwargs)
    return decorated_view

(31)登录限制+登录信息限制

def fresh_login_required(fn):
    @wraps(fn)
    def decorated_view(*args, **kwargs):
        # 用户未验证
        if not current_user.is_authenticated():
            # 重定向至login_view
            return current_app.login_manager.unauthorized()
        # 用户验证且获取session中'_fresh'的值, 如果不存在或者不新鲜
        elif not login_fresh():
            # 更新消息,重定向至login_view
            return current_app.login_manager.needs_refresh()
        return fn(*args, **kwargs)
    return decorated_view

在login_required()基础上,验证_fresh值,只允许最新登录的用户访问

(32)定义用户类

class UserMixin(object):
    def is_active(self):
        return True
    def is_authenticated(self):
        return True
    def is_anonymous(self):
        return False
    def get_id(self):
        try:
            return unicode(self.id)
        except AttributeError:
            raise NotImplementedError('NO `id` attribute -- override get_id')

flask-login提供给用户的行为方法

(33) 匿名用户类

class AnonymousUser(object):
    def is_authenticated(self):
        return False
    def is_active(self):
        return False
    def is_anonymous(self):
        return True
    def get_id(self):
        return None

(34) 信号处理
(待)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值