使用 Salt 在 Python 中创建身份验证服务
作为程序员,遇到身份验证系统是很常见的,因为如今几乎每个 Web 系统都需要控制和维护其客户的数据,并且由于其中大多数都是敏感资源,因此有必要保证它们的安全。我认为安全性,就像 API 的许多非功能性需求一样,可以通过想象各种场景来衡量或测试。例如,在身份验证服务中,我们可以思考:如果有人试图通过暴力破解用户的密码怎么办,如果另一个用户尝试使用另一个客户端的访问令牌怎么办,如果两个用户意外创建了他们的凭据怎么办使用相同的密码等
通过想象这些情况,我们可以预测并制定预防措施。例如,为密码创建标准可能会使通过暴力破解变得非常困难,或者对 API 应用速率限制可以防止恶意操作。在本文中,我打算重点讨论最后一个场景的问题。两个用户使用相同的密码在同一系统上注册是对系统的严重破坏。
在银行对用户密码进行加密是一种很好的做法,这样可以防止数据泄露。下面的代码展示了一个简单的凭证注册系统如何在 Python 中工作。
@dataclass class CreateCredentialUsecase: _credential_repository: CredentialRepositoryInterface _password_salt_repository: PasswordSaltRepositoryInterface async def handle(self, data: CreateCredentialInputDto) -> CreateCredentialOutputDto: try: now = datetime.now() self.__hash = sha256() self.__hash.update(data.password.encode()) self.__credential = Credential( uuid4(), data.email, self.__hash.hexdigest(), now, now ) credential_id = await self._credential_repository.create(self.__credential) return CreateCredentialOutputDto(UUID(credential_id)) except Exception as e: raise e
前 4 行是类定义,使用 @dataclass 装饰器省略构造函数方法、其属性和函数签名。在 try/ except 块内,首先定义当前时间戳,我们实例化 Hash 对象,使用提供的密码更新它,将其保存在银行中,最后将凭证 ID 返回给用户。在这里你可能会想“好吧......如果密码是加密的我就不需要担心,对吧?”。然而,事实并非如此,我会解释一下。
发生的情况是,当密码加密时,这是通过哈希完成的,哈希是一种将输入映射到最终值的数据结构,但是,如果两个输入相同,则会存储相同的密码。这与哈希值是确定性的相同。请注意下面的示例,该示例说明了数据库中存储用户和哈希的简单表。
user | password |
---|---|
alice@example.com | 5e884898da28047151d0e56f8dc6292773603d0d |
bob@example.com | 6dcd4ce23d88e2ee9568ba546c007c63e8f6f8d6 |
carol@example.com | a3c5b2c98b4325c6c8c6f6e6dbda6cf17b5d7f9a |
dave@example.com | 1a79a4d60de6718e8e5b326e338ae533 |
eve@example.com | 5e884898da28047151d0e56f8dc6292773603d0d |
frank@example.com | 7c6a180b36896a8a8c6a2c29e7d7b1d3 |
grace@example.com | 3c59dc048e885024e146d1e4d9d0e4b2 |
Neste exemplo, as linhas 1 e 5 compartilham o mesmo hash e, portanto, a mesma senha. Para contornarmos esse problema podemos utilizar o salt.
Vamos colocar um pouco de sal nessa senha...
A ideia é que no momento do cadastro do usuário uma string seja gerada de forma aleatória e seja concatenada a senha do usuário antes das credenciais serem salvas no banco. Em seguida esse salt é salvo em uma tabela separada e deve ser utilizada novamente durante o login do usuário. O código alterado ficaria como o exemplo abaixo:
@dataclass class CreateCredentialUsecase: _credential_repository: CredentialRepositoryInterface _password_salt_repository: PasswordSaltRepositoryInterface async def handle(self, data: CreateCredentialInputDto) -> CreateCredentialOutputDto: try: now = datetime.now() self.__salt = urandom(32) self.__hash = sha256() self.__hash.update(self.__salt + data.password.encode()) self.__credential = Credential( uuid4(), data.email, self.__hash.hexdigest(), now, now ) self.__salt = PasswordSalt( uuid4(), self.__salt.hex(), self.__credential.id, now, now ) credential_id = await self._credential_repository.create(self.__credential) await self._password_salt_repository.create(self.__salt) return CreateCredentialOutputDto(UUID(credential_id)) except Exception as e: raise e
Agora é possível notar o salt gerado na linha 59. Em seguida ele é utilizado para gerar o hash junto com a senha que o usuário cadastrou, na linha 61. Por fim ele é instanciado através da classe PasswordSalt na linha 65 e armazenado no banco na linha 70. Por último, o código abaixo é o caso de uso de autenticação/login utilizando o salt.
@dataclass class AuthUsecase: _credential_repository: CredentialRepositoryInterface _jwt_service: JWTService _refresh_token_repository: RefreshTokenRepositoryInterface async def handle(self, data: AuthInputDto) -> AuthOutputDto: try: ACCESS_TOKEN_HOURS_TO_EXPIRATION = int( getenv("ACCESS_TOKEN_HOURS_TO_EXPIRATION") ) REFRESH_TOKEN_HOURS_TO_EXPIRATION = int( getenv("REFRESH_TOKEN_HOURS_TO_EXPIRATION") ) self.__credential = await self._credential_repository.find_by_email( data.email ) if self.__credential is None: raise InvalidCredentials() self.__hash = sha256() self.__hash.update( bytes.fromhex(self.__credential.salt) + data.password.encode() ) if self.__hash.hexdigest() != self.__credential.hashed_password: raise InvalidCredentials() access_token_expiration_time = datetime.now() + timedelta( hours=( ACCESS_TOKEN_HOURS_TO_EXPIRATION if ACCESS_TOKEN_HOURS_TO_EXPIRATION is not None else 24 ) ) refresh_token_expiration_time = datetime.now() + timedelta( hours=( REFRESH_TOKEN_HOURS_TO_EXPIRATION if REFRESH_TOKEN_HOURS_TO_EXPIRATION is not None else 48 ) ) access_token_payload = { "credential_id": self.__credential.id, "email": self.__credential.email, "exp": access_token_expiration_time, } access_token = self._jwt_service.encode(access_token_payload) refresh_token_payload = { "exp": refresh_token_expiration_time, "context": { "credential": { "id": self.__credential.id, "email": self.__credential.email, }, }, } refresh_token = self._jwt_service.encode(refresh_token_payload) print(self._jwt_service.decode(refresh_token)) now = datetime.now() await self._refresh_token_repository.create( RefreshToken( uuid4(), refresh_token, False, self.__credential.id, refresh_token_expiration_time, now, now, now, ) ) return AuthOutputDto( UUID(self.__credential.id), self.__credential.email, access_token, refresh_token, ) except Exception as e: raise e
O tempo de expiração dos tokens é recuperado através de variáveis de ambiente e a credencial com o salt são recuperados através do email. Entre as linhas 103 e 106 a senha fornecida pelo usuário é concatenada ao salt e o hash dessa string resultante é gerado, assim é possível comparar com a senha armazenada no banco. Por fim acontecem os processos de criação dos access_token e refresh_token, o armazenamento do refresh_token e o retorno dos mesmos ao client. Utilizar essa técnica é bem simples e permite fechar uma falha de segurança no seu sistema, além de dificultar alguns outros possíveis ataques. O código exposto no texto faz parte de um projeto maior meu e está no meu github: https://github.com/geovanymds/auth.
Espero que esse texto tenha sido útil para deixar os processos de autenticação no seu sistem mais seguros. Nos vemos no próximo artigo!
以上是使用 Salt 在 Python 中创建身份验证服务的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。 Python以简洁和强大的生态系统着称,C 则以高性能和底层控制能力闻名。

Python在游戏和GUI开发中表现出色。1)游戏开发使用Pygame,提供绘图、音频等功能,适合创建2D游戏。2)GUI开发可选择Tkinter或PyQt,Tkinter简单易用,PyQt功能丰富,适合专业开发。

Python更易学且易用,C 则更强大但复杂。1.Python语法简洁,适合初学者,动态类型和自动内存管理使其易用,但可能导致运行时错误。2.C 提供低级控制和高级特性,适合高性能应用,但学习门槛高,需手动管理内存和类型安全。

要在有限的时间内最大化学习Python的效率,可以使用Python的datetime、time和schedule模块。1.datetime模块用于记录和规划学习时间。2.time模块帮助设置学习和休息时间。3.schedule模块自动化安排每周学习任务。

Python在开发效率上优于C ,但C 在执行性能上更高。1.Python的简洁语法和丰富库提高开发效率。2.C 的编译型特性和硬件控制提升执行性能。选择时需根据项目需求权衡开发速度与执行效率。

pythonlistsarepartofthestAndArdLibrary,herilearRaysarenot.listsarebuilt-In,多功能,和Rused ForStoringCollections,而EasaraySaraySaraySaraysaraySaraySaraysaraySaraysarrayModuleandleandleandlesscommonlyusedDduetolimitedFunctionalityFunctionalityFunctionality。

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

每天学习Python两个小时是否足够?这取决于你的目标和学习方法。1)制定清晰的学习计划,2)选择合适的学习资源和方法,3)动手实践和复习巩固,可以在这段时间内逐步掌握Python的基本知识和高级功能。
