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的扩展,执行后面的命令即可。
完成安装之后,要在config.config配置文件中配置日志级别,这里我们配置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参数,然后再传递给服务端了。
欢迎你在留言区和我交流互动,如果这节课对你有启发,也推荐你把这节课分享给更多朋友。
- 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