17.FastAPI的OAuth2的密码模式

本文介绍了FastAPI中OAuth2的密码模式和JWT令牌认证的实现过程。用户通过提供用户名和密码获取token,然后使用token访问受保护的资源。示例代码展示了如何处理登录、验证用户、生成和解析JWT token,以及获取当前活跃用户信息的流程。
摘要由CSDN通过智能技术生成

17.FastAPI的OAuth2的密码模式

  • 用户请求验证原理

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BL2l3vzz-1641212540597)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211116212043210.png)]

    • 说明

      用户携带用户名和密码去客户端请求应用,过程中会有用户认证服务,验证通过会返回用户一个token,
      用户拿着这个token就可以取去使用他想请求的东西了
      
  • 初识OAuth2的请求原理
    from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
    app06 = APIRouter()
    
    """OAuth2 密码模式和 FastAPI 的 OAuth2PasswordBearer"""
    
    """
    OAuth2PasswordBearer是接收URL作为参数的一个类:客户端会向该URL发送username和password参数,然后得到一个Token值
    OAuth2PasswordBearer并不会创建相应的URL路径操作,只是指明客户端用来请求Token的URL地址
    当请求到来的时候,FastAPI会检查请求的Authorization头信息,如果没有找到Authorization头信息,或者头信息的内容不是Bearer token,它会返回401状态码(UNAUTHORIZED)
    """
    
    oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/token")  # 请求Token的URL地址 http://127.0.0.1:8000/chapter06/token
    
    
    @app06.get("/oauth2_password_bearer")
    async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
        return {"token": token}
    
    
  • Password 和 Bearer token OAuth2的验证示例
    """基于 Password 和 Bearer token 的 OAuth2 认证"""
    
    oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/token")  # 请求Token的URL地址 http://127.0.0.1:8000/chapter06/token
    
    # 获取token
    @app06.get("/oauth2_password_bearer")
    async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
        return {"token": token}
    
    # 假设数据库存储的数据
    fake_users_db = {
        "john snow": {
            "username": "john snow",
            "full_name": "John Snow",
            "email": "johnsnow@example.com",
            "hashed_password": "fakehashedsecret",
            "disabled": False,
        },
        "alice": {
            "username": "alice",
            "full_name": "Alice Wonderson",
            "email": "alice@example.com",
            "hashed_password": "fakehashedsecret2",
            "disabled": True,
        },
    }
    
    # 数据库密码加密方式
    def fake_hash_password(password: str):
        return "fakehashed" + password
    
    # 用户模型类
    class User(BaseModel):
        username: str
        email: Optional[str] = None
        full_name: Optional[str] = None
        disabled: Optional[bool] = None
    
    # 用户数据存
    class UserInDB(User):
        hashed_password: str
    
    # token依赖的接口,需要用户名和密码验证
    @app06.post("/token")
    async def login(form_data: OAuth2PasswordRequestForm = Depends()):
        user_dict = fake_users_db.get(form_data.username)
        print(user_dict)
        if not user_dict:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
        user = UserInDB(**user_dict)
        hashed_password = fake_hash_password(form_data.password)
        if not hashed_password == user.hashed_password:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
        return {"access_token": user.username, "token_type": "bearer"}
    
    # 去数据库获取用户信息
    def get_user(db, username: str):
        if username in db:
            user_dict = db[username]
            return UserInDB(**user_dict)
    
    # 拿着token 去数据库中获取用户信息
    def fake_decode_token(token: str):
        user = get_user(fake_users_db, token)
        return user
    
    # 获取当前用户
    async def get_current_user(token: str = Depends(oauth2_schema)):
        user = fake_decode_token(token)
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid authentication credentials",
                headers={"WWW-Authenticate": "Bearer"},  # OAuth2的规范,如果认证失败,请求头中返回“WWW-Authenticate”
            )
        return user
    
    # 获取当前活跃用户
    async def get_current_active_user(current_user: User = Depends(get_current_user)):
        if current_user.disabled:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
        return current_user
    
    # 判断是否是活跃用户,如果是活跃用户则正常返回活跃用户的信息,若为非活跃用户,则报异常返回非活跃用户信息
    @app06.get("/users/me")
    async def read_users_me(current_user: User = Depends(get_current_active_user)):
        return current_user
    
    
    • 效果演示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Sh1oupo-1641212540599)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211120170557033.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUliOmrC-1641212540600)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211120170809743.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrZPxmIn-1641212540600)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211120171005421.png)]

    • http://localhost:8000/chapter06/oauth2_password_bearer 使用这个接口的查看token

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hluXYEeN-1641212540601)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211120171251089.png)]

    • http://localhost:8000/chapter06/users/me 使用这个接口判断用户是否活跃,若活跃正常返回当前用户的信息,若不活跃则抛出400异常,显示用户为非活跃用户,具体逻辑参考上面的代码。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7keLa1X-1641212540602)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211120171639090.png)]

  • Password 和hashpassword 以及Bearer with JWT tokens的OAuth2的认证
    • 认证流程图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MChWWnMC-1641212540602)(17.FastAPI%E7%9A%84OAuth2%E7%9A%84%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F.assets/image-20211121093818559.png)]

    • 代码示例
    
    """OAuth2 with Password (and hashing), Bearer with JWT tokens 开发基于JSON Web Tokens的认证"""
    from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
    from jose import JWTError, jwt
    from passlib.context import CryptContext
    from datetime import datetime, timedelta
    
    # 用户模型类
    class User(BaseModel):
        username: str
        email: Optional[str] = None
        full_name: Optional[str] = None
        disabled: Optional[bool] = None
    
    # 数据库数据解析,继承自用户模型类,新增密码属性
    class UserInDB(User):
        hashed_password: str
    
    fake_users_db = {
        "john snow": {
            "username": "john snow",
            "full_name": "John Snow",
            "email": "johnsnow@example.com",
            "hashed_password": "fakehashedsecret",
            "disabled": False,
        },
        "alice": {
            "username": "alice",
            "full_name": "Alice Wonderson",
            "email": "alice@example.com",
            "hashed_password": "fakehashedsecret2",
            "disabled": True,
        },
    }
    
    # 用户数据数据库(模拟数据库),数据库更新,修改哈希密码
    fake_users_db.update({
        "john snow": {
            "username": "john snow",
            "full_name": "John Snow",
            "email": "johnsnow@example.com",
            "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
            "disabled": False,
        }
    })
    
    '''
    # 明文密码: secret
    from passlib.context import CryptContext
    password = 'secret'
    context = dict(schemes=['bcrypt'])
    cc = CryptContext(**context)
    # 用crypt哈希过的密码,存储在数据库内
    hashed_password = cc.hash(password)
    print(hashed_password)
    '''
    
    
    SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"  # 生成密钥 openssl rand -hex 32
    
    ALGORITHM = "HS256"  # 算法
    ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 访问令牌过期分钟
    
    # 返回token的模型类
    class Token(BaseModel):
        """返回给用户的Token"""
        access_token: str
        token_type: str
    
    # 加密算法
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    
    # OAuth2的url指向,得到token
    oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/jwt/token")
    
    
    # plain_password 明文密码,hashed_password哈希密码
    def verity_password(plain_password: str, hashed_password: str):
        """对密码进行校验"""
        return pwd_context.verify(plain_password, hashed_password)
    
    # 获取用户
    def jwt_get_user(db, username: str):
        if username in db:
            user_dict = db[username]
            return UserInDB(**user_dict)
    
    # jwt 用户认证
    def jwt_authenticate_user(db, username: str, password: str):
        user = jwt_get_user(db=db, username=username)
        if not user:
            return False
        # 若用户存在,取出数据库里面的哈希密码,与传入的明文密码进行密码验证。
        if not verity_password(plain_password=password, hashed_password=user.hashed_password):
            return False
        return user
    
    # 创建token   expires_delta过期时间
    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
        to_encode = data.copy()  # {"sub": user.username}
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=15)
        # 添加过期时间,作为加密的的基础数据(用户名,和过期时间)
        to_encode.update({"exp": expire})
        # 加密,用户加密数据和secret 加密
        encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt
    
    
    @app06.post("/jwt/token", response_model=Token)
    async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
        user = jwt_authenticate_user(db=fake_users_db, username=form_data.username, password=form_data.password)
        if not user:
            raise HTTPException(
                status.HTTP_401_UNAUTHORIZED,
                detail="Incorrect username or password",
                headers={"WWW-Authenticate": "Bearer"},
            )
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        access_token = create_access_token(
            data={"sub": user.username}, expires_delta=access_token_expires
        )
        return {"access_token": access_token, "token_type": "bearer"}
    
    # 获取当前用户信息
    async def jwt_get_current_user(token: str = Depends(oauth2_schema)):
        credentials_exception = HTTPException(
            status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
        try:
            # 解密,拿token 和secret 解密 
            payload = jwt.decode(token=token, key=SECRET_KEY, algorithms=[ALGORITHM])
            # 解密取出用户名
            username = payload.get("sub")
            if username is None:
                raise credentials_exception
        except JWTError:
            raise credentials_exception
        user = jwt_get_user(db=fake_users_db, username=username)
        if user is None:
            raise credentials_exception
        return user
    
    # 获取当前活跃用户
    async def jwt_get_current_active_user(current_user: User = Depends(jwt_get_current_user)):
        if current_user.disabled:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
        return current_user
    
    # 接口
    @app06.get("/jwt/users/me")
    async def jwt_read_users_me(current_user: User = Depends(jwt_get_current_active_user)):
        return current_user
    
    • 演示略,效果和上面使用密码验证的效果差不多
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小帆芽芽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值