跳转至

27 初识认证机制:认证机制能解决哪些问题?

你好,我是Barry。今天是端午假期,祝你节日快乐,给假期仍然在学习的你点赞。

上节课,我们利用Flask-Restful实现了接口规范化开发,相信你已经可以根据功能需求独立开发接口了。

但是在项目开发中,我们单独考虑开发功能还不够,还要在用户使用系统期间,保护用户隐私和数据安全。例如在系统中需要验证用户的身份,保护用户的个人信息和隐私。这就引出了我们这节课的核心—— Flask认证机制。

认证机制能解决什么问题

接下来,我们主要从四个维度来看看,在系统中认证机制都能够解决哪些问题。

首先是保护用户隐私和访问权限。如果一个系统没有身份认证机制,用户提交的个人信息和敏感数据等就会暴露在公共网络上。这样操作会存在大量风险,例如黑客可能会利用这些信息来进行诈骗、恶意攻击以及其他非法活动。此外,系统如果没有足够的访问控制,也可能导致用户无法访问他们需要的资源或服务。

其次,要防止异常访问和攻击。在系统中有了认证机制,只有经过认证的用户才可以访问特定的资源,从而保护应用程序免受未经授权的访问和攻击。比较典型的案例就是防止恶意的 CSRF攻击,保护系统免受盗用用户身份的攻击等。

第三,就是建立用户对系统的信任。认证机制可以为系统用户提供更安全的使用体验。对用户的各类数据做好严密保护,这有助于我们建立系统和用户之间的信任关系,否则将无法留存用户。

第四是优化程序流程。一个有效的认证机制,可以让用户和应用程序之间的交互过程更加规范,这有助于提高应用程序的可靠性、稳定性和可扩展性。此外,认证机制还可以简化代码和减少开发难度,提高我们的开发效率。

通过前面这四个维度的分析,我们认识到系统中认证机制的重要性。我们这就来了解一下在Flask中都有哪些认证方式。

认证方式详解

在Flask中,我们可以根据具体应用需求选择合适的认证方式,常用的认证方式包括Cookie、Session和Token。那么三者之间在使用上有什么区别呢?只有充分了解了这三种方式,我们才能选出最适合在线视频项目的方案。

Cookie是存储在客户端浏览器中的小段文本。它的作用是保存客户端的状态信息,最主要的功能就是存储用户的认证信息。有了Cookie的帮助,请求响应结束后,服务器仍然能保存一些信息,例如记录用户登录状态等。

但是,因为在浏览器中Cookie可以轻易被修改,将明文认证信息存储在Cookie中可能存在安全风险。所以,在Flask中通常使用Session对象来加密存储Cookie数据。当然,Session对象也可以用于存储认证信息,它提供了一种轻量级的会话管理机制。同时,Session数据存储在服务器端,无法被客户端直接修改。

Session的使用当然也会存在风险,因为Session机制是基于Cookie的,Cookie被拦截时,用户容易被跨站攻击。Session一般存储在内存中,每个用户通过认证之后,都会将Session数据保存在服务器的内存中,所以用户量达到一定程度时就会给服务器造成负担。

了解了Cookie和Session的认证方式,接下来我们再看看Token的认证方式。基于Token的认证机制工作原理是将认证信息返回给客户端,由客户端自己保存,等待访问服务器请求资源的同时,带上Token认证信息就可以。

如果我们采用这样的方式,服务器端就不需要保留用户的认证或者是会话等信息。对用户而言,不需要指定请求存储了用户认证信息的某个特定服务器,可以在多个服务器之间使用APP,在增加更多的用户或者更多的功能时,就更容易扩展应用程序。

综合前面的分析,我们的项目中最终选择Token的认证方式。

认识Token

在Flask中,Token可以分为两种类型,分别是JWT(JSON Web Token)和自定义Token。

JWT是一种开放标准,它提供了一种轻量级且安全的身份验证方法。JWT分成三个部分,分别是header(头部)、payload(载荷)和signature(签名)。具体含义和用途你可以参考后面的表格。

在项目中使用JWT完成Token认证的好处是:它可以在不同系统之间传递,并且不需要依赖数据库或其他身份验证机制。

在了解了JWT之后,我们紧接着来认识一下自定义Token,对比一下二者的区别。自定义Token是我们根据应用程序需求自己设计的Token。它通常包含用户ID、过期时间、角色等信息,同时还可以包括用于验证Token完整性和真实性的签名。当然了,我们也可以结合自己的需求自定义其他参数进行传递。

我们之所以使用自定义Token,是因为它可以根据项目需求去灵活设计Token的组成结构和验证机制。我们在使用的过程中还需要注意,自定义Token需要实现完整的Token验证逻辑,其中包括签名验证、过期时间检查、防止Token泄露等安全措施。

说完了两种Token形式之后,接下来,我们看看如何通过Token实现认证机制。

Token的认证流程

在Flask中,Token认证机制可以用于保护项目的敏感资源,限制用户只有经过身份验证后才能访问与其相关的资源。

Token认证是一种基于令牌的身份验证方法,它在客户端和服务器之间传递一个加密的令牌,以此来实现用户信息的认证。为了帮你直观理解认证流程。我为你准备了后面这张认证机制流程图。

首先我们需要生成Token。在用户成功登录系统后,后端会生成相应的Token,并将Token存储在服务器端,这里可以选择数据库或内存缓存来存储。

紧接着,在用户访问需要认证的资源时,服务端将 Token发送给客户端。这一步传送的实现方式就是将Token存储在Cookie或HTTP Header中。在客户端发送请求时,服务端可以通过解析Token、检查过期时间、验证签名等方式来验证Token的完整性和真实性,从而建立起认证机制。

当然我们为了确保Token的安全性,服务端需要实现Token的过期时间和失效机制。当Token过期或被篡改时,应用程序需要更新或删除相应的Token,从而保证认证的时效性。

这里还需要注意的一个地方就是,Token 认证中有两个必要组件——日志和内存,它们分别用于记录认证过程、存储令牌及用户信息。两者有助于保证系统的安全性,让系统正常运行。接下来我们就一起了解一下日志和内存的工作原理。

日志和内存的工作原理

日志主要用来记录认证过程中的一些事件,例如系统中的用户访问受保护的资源事件、用户信息认证成功或失败等等。通过记录这些信息,可以让我们更好地了解应用程序的安全性和可能潜在的攻击问题。当发生异常或攻击时,日志还可以帮助开发人员快速定位问题并采取相应的措施。

实现日志记录

那么如何在项目中实现日志的记录呢?在Flask中我们可以使用 Python 的内置 logging 模块来实现记录日志。你可以对照表格看看具体的日志级别分类,从上往下级别依次升高,最高的是CRITICAL。

话不多说,我们进入实操环节。首先,我们需要安装logging的扩展,执行后面的命令即可。

pip install logging

完成安装之后,要在config.config配置文件中配置日志级别,这里我们配置INFO等级。

LEVEL_LOG = logging.INFO

还有一步不能忘记,就是给生产环境的config也设置日志级别。

class ProConfig(Config):
    LEVEL_LOG = logging.ERROR
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:flask_project@127.0.0.1:3306/aaa"

紧接着,我们在api/utils目录下,新建log_utils.py文件。在文件中定义三种不同的日志格式和输出方式,用来满足不同的日志记录需求。

三种输出模式都是什么作用呢?setup_log函数将日志记录到文件中,setup_logger函数同时记录日志到文件和控制台上,而json_log函数则是将日志以json格式记录到文件中。

接下来。我们再来看看它们是如何将日志文件写入文件中的。这里主要借助RotatingFileHandle函数,它是Python 的logging模块中的一个处理器,可以把日志信息写到文件中,并且支持日志滚动,也就是当文件大小达到一定阈值时,会自动创建一个新的日志文件,避免单个日志文件过大造成的存储问题。

对于日志文件过多而导致存储空间被占用的这个问题,通过日志的滚动就能解决。日志滚动是指将旧的日志文件删除,并将新的日志文件添加到日志滚动组中。

通过指定文件的最大量、备份数量等参数,就可以控制日志的滚动。具体的日志应用代码案例如下所示。

def setup_log(logger_name=None, log_file='logs/log', level=logging.INFO):
    """根据创建app时的配置环境,加载日志等级"""
    # 设置日志的记录等级
    logging.basicConfig(level=level)  # 调试debug级
    # 创建日志记录器,指明日志保存的路径、每个日志文件的最大量、保存的日志文件个数上限
    file_log_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024 * 100, backupCount=10)
    # 创建日志记录的格式      日志等级    输入日志信息的文件名 行数    日志信息
    formatter = logging.Formatter('%(asctime)s - %(levelname)s %(filename)s:%(lineno)d %(message)s')
    # 为刚创建的日志记录器设置日志记录格式
    file_log_handler.setFormatter(formatter)
    # 为全局的日志工具对象(flask app使用的)添加日志记录器
    logging.getLogger(logger_name).addHandler(file_log_handler)

在上面代码中,我们首先要设置日志的记录等级为INFO。随后再创建一个 RotatingFileHandler 对象来记录日志,里面需要设置日志文件的路径、每个日志文件的最大量为 100 MB,最多保存 10 个文件。随后是创建日志记录的格式,这里采用字符串的格式。最后,将日志记录器添加到全局的日志工具对象中,这样我们就完成了日志应用的配置代码。

那么如何同时记录日志到文件和控制台上呢?这时我们可以使用logging模块中的FileHandler和StreamHandle,将日志同时写入文件和控制台,具体的代码实现是后面这样。

def setup_logger(logger_name, log_file, level=logging.INFO):
    """
    %(asctime)s 即日志记录时间,精确到毫秒
    %(levelname)s 即此条日志级别
    %(filename)s 即触发日志记录的python文件名
    %(funcName)s 即触发日志记录的函数名
    %(lineno)s 即触发日志记录代码的行号
    %(message)s 这项即调用如app.logger.info(‘info log’)中的参数,即message
    :param logger_name:
    :param log_file:
    :param level:
    :return:
    """
    log = logging.getLogger(logger_name)
    # 创建日志对象
    formatter = logging.Formatter('%(asctime)s : %(message)s')
    # 设置格式,'% (asctime) s: %(message) s',即日志记录时间和日志内容
    file_handler = logging.FileHandler(log_file, mode='w')
    # 创建一个文件处理器,将日志内容输出到名为log_file的文件中。
    file_handler.setFormatter(formatter)
    # 将日志格式设置为文件处理器的格式
    stream_handler = logging.StreamHandler()
    # 创建一个流处理器,将日志内容输出到控制台
    stream_handler.setFormatter(formatter)
    # 将日志格式设置为流处理器的格式
    log.setLevel(level)
    # 将日志级别设置为传入的参数level
    log.addHandler(file_handler)
    log.addHandler(stream_handler)
    # 将文件处理器和流处理器添加至日志对象中

代码整体的流程和将日志文件写入文件是类似的,setup_logger函数会把日志记录器的名称、记录文件的名称和记录级别作为参数。我们先创建日志对象,然后再设置日志格式。紧接着就是创建文件处理器和流处理器,最后将两个处理器添加到日志对象中。

我们将日志内容输出到文件时,设置了参数mode=‘w’,这表示以写入方式打开文件,这种模式会先清空文件中的内容,然后写入新的内容。如果写入的时候发现文件不存在,系统就会创建一个新文件。

当然,mode的值除了是 ‘W’ 以外还包括其他的类型,例如后面这些。

1.mode= ‘a’ 表示以追加模式打开日志文件,新写入的日志会添加到文件末尾。
2.mode=‘x’ 表示以排他模式打开日志文件,如果文件已经存在就会抛出 FileExistsError 异常。
3.mode=‘b’ 表示以二进制模式打开日志文件,适用于 Windows 文件系统。

Redis存储

清楚了日志的作用具体用法,我们继续来看Token认证中的内存。

Token 认证通常使用一个内存存储来保存生成的 Token。项目中对内存部分的操作,我们借助选用Redis来实现。使用Redis可以用来存储用户Token和相关的用户信息。

我们先来了解一下它的工作原理,当用户登录成功后,服务器会生成一个Token并将其存储到Redis中,同时将Token返回给客户端。客户端在后续的请求中,需要在请求头中携带该Token,服务器在接收到请求后,会检查该Token是否在Redis中存在,并获取相关的用户信息。

如果Token验证成功,即认为该请求是来自已登录的用户,服务器会返回相应的数据;否则服务器就要返回相应的错误信息。

使用Redis可以简单而高效地实现Token认证操作,避免在每个接口请求中都需要去查询数据库的开销,这样会大大提高系统的性能。

我们这就来看看具体的配置实现。第一步就是在config.config配置文件中进行设置。后续我们还会使用到Session来存储客户端与服务器交互时的一些信息,这里我们一并配置完成。具体的代码是后面这样。

REDIS_HOST = '127.0.0.1'
# Redis服务器的IP地址和端口号
REDIS_PORT = 6379

# 指定session使用什么来存储
SESSION_TYPE = 'redis'
# 指定session数据存储在后端的位置
SESSION_REDIS = StrictRedis(host=REDIS_HOST, port=REDIS_PORT)
# 是否使用secret_key签名你的sessin
SESSION_USE_SIGNER = True
# 设置过期时间,要求'SESSION_PERMANENT', True。而默认就是31天
PERMANENT_SESSION_LIFETIME = 60 * 60 * 24  # 一天有效期

第二步,我们需要在api/init.py文件中,设置创建Redis的连接对象。

redis_store = None
global redis_store
# 创建redis的连接对象
redis_store = StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT, decode_responses=True)

到这里,我们就完成了使用Token进行认证的前置工作。通过上面的学习,你已经掌握了Token认证的两个核心配置应用。日志用来记录认证过程中的一些事件,而Redis用来存储用户Token和相关的用户信息。

总结

又到了课程的尾声,我们来总结一下这节课重点。

认证机制在项目中不可或缺,它极大地保护了用户在平台内的安全性。Flask里常用的认证方式包括使用Cookie、Session和Token。

Session和Cookie的方式下,服务器必须存储用户的认证信息,这样用户量增加时服务器负荷就会增加。而Token很好地解决了这一问题,只在用户登录时,将Token临时存放在内存中,用于快速验证。把Token发给各个客户端,解决了应用程序的扩展问题。

随后,我们学习了Token里的三个重要部分,主要信息存储在payload中,我们借助JWT中的encode函数并且使用密钥来生成Token。解码时则要借助函数decode,这个过程同样需要使用到密钥。

日志和Redis内存是Token认证中的重点。日志方面,你需要重点掌握编写日志的几种模式对应的代码。内存方面,使用redis_store扩展管理内存的过程里,我们重点要关注如何在config配置文件中做好设置,建议你自己在课后也尝试练习一下。

思考题

既然我们要通过Token进行用户认证,请你思考一下在调用接口的时候怎样实现统一化管理呢?这样我们不用每次请求都需要在代码中写一遍Token参数,然后再传递给服务端了。

欢迎你在留言区和我交流互动,如果这节课对你有启发,也推荐你把这节课分享给更多朋友。

精选留言(2)
  • ZENG 👍(1) 💬(1)

    思考题:使用请求拦截,每次请求接口可以带上token等信息 老师,我这边有几点不太明白 1、使用token服务端不需要保存用户认证和会话,但是又需要内存存token,在过期时间之前都会保存这个token吗,理论上如果用户量太多内存也会有压力吧 2、如果发给客户端的token保存在cookie中,可能也会被其他人拿到伪造用户登录行为,这个如何避免呢(我想到就是可以在荷载数据中加入ADDR、UA这样的信息来识别) 3、前端发来的token如果还没到过期时间,但是redis又查不到,可不可以通过解密token然后根据荷载数据的某些信息通过mysql去查数据返回呢

    2023-06-23

  • peter 👍(0) 💬(1)

    请教老师两个问题: Q1:OAuth2会用到吗? Q2:http header中保存token,是保存在哪个字段? 另外,token保存在cookie中,session也是基于cookie,token与session有什么区别?

    2023-06-23