SQLAlchemy 将密码转换为十六进制值
SQLAlchemy converts Password to HEX Value
我在我的一个项目中使用 Sanic + SQLAlchemy。
基本上,我有这个用于注册用户的端点
from sanic import Blueprint, response
from sanic.request import Request
from sanic.response import HTTPResponse
import bcrypt
import logging
from sanic_cors import CORS, cross_origin
from services.userservice import create_user, find_by_username
@bp.route("/add",methods=["POST", "OPTIONS"])
@cross_origin(bp, origins='*',automatic_options=True)
async def register(req: Request) -> HTTPResponse:
"""
Register the user in the system
:param req: the request sent from the user. Will contain the necessary parameters to save the user in the system
:return 200 if the user is registered, 500 if an error happens
"""
LOGGER.info(">>>>>> /user/add")
chosen_username = req.json['utente']['username']
chosen_pwd = req.json['utente']['password']
salt = bcrypt.gensalt(rounds = 10, prefix=b"2a")
hashed = bcrypt.hashpw(chosen_pwd.encode('utf-8'), salt)
insert_success = await create_user(chosen_username,hashed,req.app.session_pgsql)
if insert_success:
LOGGER.info(f"[{chosen_username}] successfully created!")
created_user = await find_by_username(chosen_username, req.app.session_pgsql)
return response.json({'status': 200, 'message': 'OK', 'data': created_user}, 200)
else:
LOGGER.error(f"Couldn't add {chosen_username} to the database!")
return response.json({'status':500, 'message': 'ERROR', 'data': None},500)
bcrypt 以这种方式实例化盐,因为身份验证过程由 Java 微服务执行。
然后,当代码到达create_user方法时
async def create_user(chosen_username: str, hashed: str, session_pgsql: Session) -> bool:
"""
Creates the specified user in the database
:param chosen_username: the chosen username
:param hashed: the hashed password that the new user chose
:param session_pgsql: the SQLAlchemy Session Object that will insert the user in the DB
:return True if the insert was successful, False otherwhise
"""
new_user = Utente(username=chosen_username, pwd=hashed)
session_pgsql.add(new_user)
try:
session_pgsql.commit()
except Exception as e:
session_pgsql.rollback()
return False
return True
保存操作进行得很顺利,但是在我的 PostgreSQL 数据库中(使用 postgresql+psycopg2 驱动程序 URL 配置建立连接),我看到密码已保存作为十六进制字符序列。
例如,即使我尝试
session_pgsql.query(Utente).filter(Utente.id == new_user.id).update({Utente.pwd : hashed}, synchronize_session = False)
或
new_user.pwd = hashed
session_pgsql.commit()
在 PyCharm 的调试模式下,密码仍然显示为十六进制字符序列。
有人可能对发生的事情有一个模糊的线索吗?据我在调试中看到的,密码正确到达 create_user 方法,所以我认为问题必须依赖于 SQLAlchemy 内部
Utente 模型是这样的class
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import relationship
Base = declarative_base()
"""
The ORM class that represents the 'Utente' table
"""
class Utente(Base):
__tablename__ = 'utente'
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
pwd = Column(String)
enabled = Column(Boolean, default=False)
first_access_done = Column(Boolean, default=False)
编辑:根据要求,这是我的数据发生的情况。
在提交数据库之前,Utente 对象看起来像这样。
new_user.username
Out[4]: 'acooltestmail@gmail.com'
In[5]:
new_user.pwd
Out[5]: b'a$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe'
然后,这个查询由SQLAlchemy执行
2020-06-05 20:27:02,973 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-06-05 20:27:03,016 INFO sqlalchemy.engine.base.Engine INSERT INTO utente (username, pwd, enabled, first_access_done) VALUES (%(username)s, %(pwd)s, %(enabled)s, %(first_access_done)s) RETURNING utente.id
2020-06-05 20:27:03,016 INFO sqlalchemy.engine.base.Engine {'username': 'acooltestmail@gmail.com', 'pwd': b'a$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe', 'enabled': False, 'first_access_done': False}
2020-06-05 20:27:03,037 INFO sqlalchemy.engine.base.Engine COMMIT
但是如果我去数据库中查看结果查询,结果实际上是这样的
id|username |pwd |enabled|first_access_done|
--|----------------------|--------------------------------------------------------------------------------------------------------------------------|-------|-----------------|
5|acooltestmail@gmail.com|\x2432612431302457684632434243436d314d4c616f4f6c775278345965412e754d514e744c3358484f49366930395a62576b4b447035687841324665|false |false |
最后恰好是我这边的失误。
当我看到 bcrypt 的哈希结果在调试器中呈现为 b'$2a$10$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe' 时,我产生了疑问。
我相信 bcrypt 的 hashpw
方法返回一个 string
,而它实际上 returns 一个 bytes
对象。因此,如果保存到 PostgreSQL
varchar
字段,数据库会尝试将该值转换为实际字符串(就像 Ilja Everilä 在评论中建议的那样)。
我在我的一个项目中使用 Sanic + SQLAlchemy。
基本上,我有这个用于注册用户的端点
from sanic import Blueprint, response
from sanic.request import Request
from sanic.response import HTTPResponse
import bcrypt
import logging
from sanic_cors import CORS, cross_origin
from services.userservice import create_user, find_by_username
@bp.route("/add",methods=["POST", "OPTIONS"])
@cross_origin(bp, origins='*',automatic_options=True)
async def register(req: Request) -> HTTPResponse:
"""
Register the user in the system
:param req: the request sent from the user. Will contain the necessary parameters to save the user in the system
:return 200 if the user is registered, 500 if an error happens
"""
LOGGER.info(">>>>>> /user/add")
chosen_username = req.json['utente']['username']
chosen_pwd = req.json['utente']['password']
salt = bcrypt.gensalt(rounds = 10, prefix=b"2a")
hashed = bcrypt.hashpw(chosen_pwd.encode('utf-8'), salt)
insert_success = await create_user(chosen_username,hashed,req.app.session_pgsql)
if insert_success:
LOGGER.info(f"[{chosen_username}] successfully created!")
created_user = await find_by_username(chosen_username, req.app.session_pgsql)
return response.json({'status': 200, 'message': 'OK', 'data': created_user}, 200)
else:
LOGGER.error(f"Couldn't add {chosen_username} to the database!")
return response.json({'status':500, 'message': 'ERROR', 'data': None},500)
bcrypt 以这种方式实例化盐,因为身份验证过程由 Java 微服务执行。
然后,当代码到达create_user方法时
async def create_user(chosen_username: str, hashed: str, session_pgsql: Session) -> bool:
"""
Creates the specified user in the database
:param chosen_username: the chosen username
:param hashed: the hashed password that the new user chose
:param session_pgsql: the SQLAlchemy Session Object that will insert the user in the DB
:return True if the insert was successful, False otherwhise
"""
new_user = Utente(username=chosen_username, pwd=hashed)
session_pgsql.add(new_user)
try:
session_pgsql.commit()
except Exception as e:
session_pgsql.rollback()
return False
return True
保存操作进行得很顺利,但是在我的 PostgreSQL 数据库中(使用 postgresql+psycopg2 驱动程序 URL 配置建立连接),我看到密码已保存作为十六进制字符序列。 例如,即使我尝试
session_pgsql.query(Utente).filter(Utente.id == new_user.id).update({Utente.pwd : hashed}, synchronize_session = False)
或
new_user.pwd = hashed
session_pgsql.commit()
在 PyCharm 的调试模式下,密码仍然显示为十六进制字符序列。
有人可能对发生的事情有一个模糊的线索吗?据我在调试中看到的,密码正确到达 create_user 方法,所以我认为问题必须依赖于 SQLAlchemy 内部
Utente 模型是这样的class
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import relationship
Base = declarative_base()
"""
The ORM class that represents the 'Utente' table
"""
class Utente(Base):
__tablename__ = 'utente'
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
pwd = Column(String)
enabled = Column(Boolean, default=False)
first_access_done = Column(Boolean, default=False)
编辑:根据要求,这是我的数据发生的情况。
在提交数据库之前,Utente 对象看起来像这样。
new_user.username
Out[4]: 'acooltestmail@gmail.com'
In[5]:
new_user.pwd
Out[5]: b'a$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe'
然后,这个查询由SQLAlchemy执行
2020-06-05 20:27:02,973 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-06-05 20:27:03,016 INFO sqlalchemy.engine.base.Engine INSERT INTO utente (username, pwd, enabled, first_access_done) VALUES (%(username)s, %(pwd)s, %(enabled)s, %(first_access_done)s) RETURNING utente.id
2020-06-05 20:27:03,016 INFO sqlalchemy.engine.base.Engine {'username': 'acooltestmail@gmail.com', 'pwd': b'a$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe', 'enabled': False, 'first_access_done': False}
2020-06-05 20:27:03,037 INFO sqlalchemy.engine.base.Engine COMMIT
但是如果我去数据库中查看结果查询,结果实际上是这样的
id|username |pwd |enabled|first_access_done|
--|----------------------|--------------------------------------------------------------------------------------------------------------------------|-------|-----------------|
5|acooltestmail@gmail.com|\x2432612431302457684632434243436d314d4c616f4f6c775278345965412e754d514e744c3358484f49366930395a62576b4b447035687841324665|false |false |
最后恰好是我这边的失误。
当我看到 bcrypt 的哈希结果在调试器中呈现为 b'$2a$10$WhF2CBCCm1MLaoOlwRx4YeA.uMQNtL3XHOI6i09ZbWkKDp5hxA2Fe' 时,我产生了疑问。
我相信 bcrypt 的 hashpw
方法返回一个 string
,而它实际上 returns 一个 bytes
对象。因此,如果保存到 PostgreSQL
varchar
字段,数据库会尝试将该值转换为实际字符串(就像 Ilja Everilä 在评论中建议的那样)。