• FastAPI安全系列(三) 基于Hash Password和JWT Bearer Token的OAuth2 .0认证


    一、准备

    1、python-jose

    JavaScript对象签名和加密(JOSE)技术。

    • JSON Web Signatures(JWS)
    • JSON Web Encryption(JWK)
    • JSON Web Key(JWK)
    • JSON Web Algorithms(JWA)

    使用各种算法对内容进行加密和签名,其中JSON Web Signatures是对JSON编码对象进行签名,然后将其编成复杂的URL安全字符串。支持的算法有:

    Algorithm Value Digital Signature or MAC Algorithm
    HS256 HMAC using SHA-256 hash algorithm
    HS384 HMAC using SHA-384 hash algorithm
    HS512 HMAC using SHA-512 hash algorithm
    RS256 RSASSA using SHA-256 hash algorithm
    RS384 RSASSA using SHA-384 hash algorithm
    RS512 RSASSA using SHA-512 hash algorithm
    ES256 ECDSA using SHA-256 hash algorithm
    ES384 ECDSA using SHA-384 hash algorithm
    ES512 ECDSA using SHA-512 hash algorithm

    在使用前先进行安装包:

    pip insstall python-jose

    然后使用:

    >>> from jose import jwt
    >>> token = jwt.encode({"key":"value"},"secret",algorithm="HS256")
    >>> token
    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.FG-8UppwHaFp1LgRYQQeS6EDQF7_6-bMFegNucHjmWg'
    >>> jwt.decode(token,"secret",algorithms=["HS256"])
    {'key': 'value'}

    2、passlib

      passlib是一个用于python2 & 3的密码散列包,它提供了超过30多种密码散列算法,同时也是一个管理现有密码散列的框架。

      passlib可以大致分为四类:

    • Password Hashes
    • Password Contexts
    • Two-Factor Authentication
    • Application Helpers

    使用这个模块,需要先安装passlib:

    pip install passlib

    然后这里以Password Hashes为例:

    # 加密
    >>> from passlib.hash import pbkdf2_sha256
    >>> hash = pbkdf2_sha256.hash("password")
    >>> hash
    '$pbkdf2-sha256$29000$8l4rpTRmDGEsRUhJac05Bw$eajW7PFThCDyQ2DiCbVIeaMw6pF/bLXj5XkLlI3dOY0'
    
    # 进行验证
    >>> pbkdf2_sha256.verify("password", hash)
    True

    二、Hash Password和JWT Bearer Token认证

    (一)流程

    •  客户端发送用户名和密码到生成token的路径操作
    • 服务器路径操作函数生成对应的JWT Token
    • 返回JWT Token到客户端
    • 客户端发送请求,并且请求头中携带对应的Token
    • 服务端检查JWT Token,并且从Token中得到用户信息
    • 服务端将用户信息返回到客户端

    (二)获取jwt token

    from pydantic import BaseModel
    from fastapi import FastAPI, Depends, HTTPException, status
    from typing import Optional
    from passlib.context import CryptContext
    from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
    from datetime import datetime, timedelta
    from jose import jwt, JWTError
    
    app = FastAPI()
    
    fake_users_db = {
        "johndoe": {
            "username": "johndoe",
            "full_name": "John Doe",
            "email": "johndoe@example.com",
            "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
            "disabled": False,
        }
    }
    
    SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
    ALGORITHM = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTE = 30
    
    
    class User(BaseModel):
        username: str
        email: Optional[str] = None
        full_name: Optional[str] = None
        disabled: Optional[bool] = None
    
    
    class UserInDB(User):
        hashed_password: str
    
    
    class Token(BaseModel):
        """返回给用户的Token"""
        access_token: str
        token_type: str
    
    
    oauth2_schema = OAuth2PasswordBearer(tokenUrl="/jwt/token")
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    
    
    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)
    
    
    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
    
    
    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
        """用户验证成功,并且取出用户信息,然后据此创建jwt token"""
        to_encode = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=15)
        to_encode.update({"exp": expire})
        encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt
    
    
    @app.post("/jwt/token", response_model=Token)
    async def login_jwt_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_code=status.HTTP_401_UNAUTHORIZED,
                detail="Incorrect username or password",
                headers={"WWW-Authenticate": "Bearer"}
            )
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTE)
        access_token = create_access_token(
            data={"sub": user.username},
            expires_delta=access_token_expires
        )
        return {"access_token": access_token, "token_type": "bearer"}

    在交互文档中输入用户名和密码获取jwt token:

    (三)获取活跃用户

    from pydantic import BaseModel
    from fastapi import FastAPI, Depends, HTTPException, status
    from typing import Optional
    from passlib.context import CryptContext
    from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
    from datetime import datetime, timedelta
    from jose import jwt, JWTError
    
    app = FastAPI()
    
    fake_users_db = {
        "johndoe": {
            "username": "johndoe",
            "full_name": "John Doe",
            "email": "johndoe@example.com",
            "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
            "disabled": False,
        }
    }
    
    SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
    ALGORITHM = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTE = 30
    
    
    class User(BaseModel):
        username: str
        email: Optional[str] = None
        full_name: Optional[str] = None
        disabled: Optional[bool] = None
    
    
    class UserInDB(User):
        hashed_password: str
    
    
    class Token(BaseModel):
        """返回给用户的Token"""
        access_token: str
        token_type: str
    
    
    oauth2_schema = OAuth2PasswordBearer(tokenUrl="/jwt/token")
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    
    
    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)
    
    
    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
    
    
    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
        """用户验证成功,并且取出用户信息,然后据此创建jwt token"""
        to_encode = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=15)
        to_encode.update({"exp": expire})
        encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt
    
    
    @app.post("/jwt/token", response_model=Token)
    async def login_jwt_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_code=status.HTTP_401_UNAUTHORIZED,
                detail="Incorrect username or password",
                headers={"WWW-Authenticate": "Bearer"}
            )
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTE)
        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_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Bearer"}
        )
        try:
            payload = jwt.decode(token=token, key=SECRET_KEY, algorithms=[ALGORITHM])
            username = payload.get("sub")
            if not username:
                raise credentials_exception
        except JWTError:
            raise credentials_exception
        user = jwt_get_user(db=fake_users_db, username=username)
        if not user:
            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
    
    
    @app.get("/jwt/users/me")
    async def jwt_read_users_me(current_user: User = Depends(jwt_get_current_active_user)):
        return current_user
    作者:iveBoy
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    推荐系统和业务系统对比:
    认识事物的过程是:
    思考:面向对象源码的解析和阅读需要注意和把握的点
    推荐系统中ES使用过程中遇到的问题:
    使用缓存功能要掌握住(心里有数)的点:
    思考:延迟决策是非常重要的
    压力测试中tps上不去的原因
    mysql 高级查询二
    mysql 高级查询
    fiddler设置https抓包
  • 原文地址:https://www.cnblogs.com/shenjianping/p/14870309.html
Copyright © 2020-2023  润新知