fastapi项目 03-注册,密码加密
1. 前言
一般对于后端的接口,特别是注册接口而言,密码都不是明文存储的,而是通过加密的方式,存储加密后的密码的。
1.1 环境准备
我们需要下载第三方加密库:> pip install passlib
passlib 库里面会用到2个方法
- encrypt() - 生成新的值,返回密码哈希
- verify() - 根据现有哈希验证密码.
Passlib是Python 2和3的密码散列库,它提供了30多种密码散列算法的跨平台实现,以及管理现有密码散列的框架。它被设计用于广泛的任务,从验证/etc/shadow中的散列,到为多用户应用程序提供全强度密码散列。
from passlib.hash import pbkdf2_sha256
password = "dack"
hash_p = pbkdf2_sha256.hash("dack")
print(f"加密后:{hash_p}")
我们多运行几次,发现每次得到的结果都不一样
加密后:$pbkdf2-sha256$29000$yBljbE3JGSPknHOu1VqrtQ$YN4k3VHR.lK1eUMrbrILP2JbIq0MkBArGvgCamaqG2c
加密后:$pbkdf2-sha256$29000$PkdIKcXYuxdizDkHIGQsRQ$XX5jghW9/Ez10fsIculxWX7PZ8A5upjx0cXs.2Wd7HE
这样就会让别人很难破解出你的原始密码内容,在数据库我们就可以保存加密后的值。当用户传一个密码值过来的时候,可以用verify() 方法验证密码是否正确。
# 验证密码
result1 = pbkdf2_sha256.verify("dack1", hash_p)
print(result1)
result2 = pbkdf2_sha256.verify("dack", hash_p)
print(result2)
# 运行结果
False
True
除了上面用到的pbkdf2_sha256 加密方式,它提供了30多种密码散列算法,使用方法都差不多,比如用sha256_crypt。
from passlib.hash import sha256_crypt
password = "dack"
hash_p = sha256_crypt.hash("dack")
print(f"加密后:{hash_p}")
# 验证密码
result1 = sha256_crypt.verify("dack1", hash_p)
print(result1)
result2 = sha256_crypt.verify("dack", hash_p)
print(result2)
# 运行结果:
加密后:$5$rounds=535000$BhJ3gP9aySdAmAdc$e5h9KJVtmmkhtT/W0RyoBsYrXChnC.aU7zLpaZV6X1B
False
True
2. models模型,对密码加密
# apps/model/models.py
def hash_password(self, password: str):
"""密码加密"""
self.password = sha256_crypt.encrypt(password)
def verify_password(self, password: str):
"""校验密码"""
return sha256_crypt.verify(password, self.password)
创建用户数据时,调用hash_password() 方法。
# apps/routers/view_login.py
from typing import Union
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field, EmailStr
class UserInfo(BaseModel):
username: str = Field(..., title="用户名", description="用户名", min_length=3, max_length=10)
password: str = Field(..., title="密码", description="密码", min_length=6, max_length=16)
email: Union[EmailStr, None] = Field(default=None, title="邮箱")
@router.post("/register")
async def register_demo(body: UserInfo, db: Session = Depends(get_db)):
# db的实例
print(f"请求body:{body}")
user = User(**body.dict())
if db.query(User).filter(User.username == user.username).count():
# 已被注册
raise HTTPException(status_code=400, detail="username already exist")
# 密码加密
user.hash_password(user.password)
print(f'数据入库前:{user.username}')
db.add(user)
db.commit()
db.refresh(user)
return {"code": 0000, "body": user}
运行main.py文件。通过postman进行测试是否密码加密成功。
3. 封装crud,数据模型
上面都是将数据库的操作和数据的模型写在一个文件夹里面,这样会显示看着很乱。于是需要单独创建一个crud.py文件处理数据库的增删改查操作。创建schemas.py文件单独处理数据的模型。
"""数据库相关操作"""
# apps/model/crud.py
from apps.model.models import User
from fastapi import HTTPException
def is_user_exist(db, user):
if db.query(User).filter_by(username=user.username).count() > 0:
return True
else:
return False
def create_user(db, user):
db_user = User(**user.dict())
print(f'获取到user的数据模型:{db_user}')
# 密码加密
db_user.hash_password(user.password)
print(f'数据入库前:{user.username}')
try:
db.add(db_user)
db.commit()
db.refresh(db_user)
except Exception as e:
print(f'数据写入失败:{e}')
HTTPException(status_code=400, detail='用户注册失败!')
# apps/model/schemas.py
"""定义数据模型"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
class UserIn(BaseModel):
username: str = Field(..., title="用户名", description="用户名", min_length=3, max_length=10)
password: str = Field(..., title="密码", description="密码", min_length=4, max_length=16)
email: Optional[EmailStr] = None
在view接口函数中,对接口函数进行优化如下,这样的话,整体看起来就比较清晰有条理。
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from apps.dependency import get_db
from apps.model import crud
from apps.model.schemas import UserIn
router = APIRouter()
@router.post("/login")
async def login_demo():
return {"msg": "login success!"}
@router.post("/register")
async def register_demo(body: UserIn, db: Session = Depends(get_db)):
# db的实例
if crud.is_user_exist(db, body):
# 已被注册
raise HTTPException(status_code=400, detail="username already exist")
# 注册用户写入到数据库中
crud.create_user(db, body)
return {"code": 0000, "msg": 'ok'}