fastapi user permissions, login, exit login component

will be in front of the words

The default components of fastapi are actually available on the official website. Regarding permissions and so on, here is the introduction to FastAPI security.
However, during the learning process, I found that the default components officially provided by it have the following flaws ( of course these are some of my personal opinions, maybe I haven’t learned enough about FastAPI itself and haven’t gotten some of its knowledge points. If someone has a better solution, I hope they can put it in the comment area) :

  1. Key information such as user name, expiration time, etc. are stored on the client, and the default jwt is used. There is basically no encryption and there are security risks.
  2. The default component does not refresh the expiration time of user verification information. The duration of each login session is fixed, rather than refreshing the duration as the user accesses.
  3. The functions provided are not perfect. If you add different permissions, there is still a lot of work.
  4. By default, the authorization code is placed in the requests headers, which is inconvenient in many cases. It is mainly because you are used to cookies. After all, the browser will automatically carry the cookie information.

So I flask-loginwrote a permission component using the Fastapi dependency method. Considering that this part of the component may still be used in future development, I uploaded it specially.

The first section is the effect of using it, and the follow-up is the specific implementation process. If you want to read in detail, it is recommended to read the first section at the end.

1. Use in view

  • First import the instantiated rights management object
from ..permission import role_required
  • When a view requires a certain permission to access, we can call it like this:
    through the dependency of the fastapi framework, we can first obtain the current user information through the permission component, and pass different parameters to lock different permission requirements. If If the permissions are insufficient, an incorrect status code will be returned.
@router.get("/")
async def read_users(
    current_user: schemas.PyUser=Depends(role_required("管理员"))
):
    print(current_user)
    return [{
    
    "username": "Foo"}, {
    
    "username": "Bar"}]
  • When logging in, register the current user
    and role_required.login(response, current_user)call it to register the current user into the permission component, so that when the current user accesses other routes that require permissions, the permissions will be automatically determined.
@router.post("/login/")
async def login(
    user:schemas.PyUserLogin, 
    response: Response,
    session: Session = Depends(get_db),
):
    dbuser = session.query(db.User).filter(db.User.username == db.User.username).first()
    if not (dbuser and dbuser.verify_password(user.password)):
        raise ApiException(
            code = 1001,
            message = "账号不存在或密码错误"
        )
    current_user = schemas.PyUser.from_orm(dbuser)
    role_required.login(response, current_user)
    return ApiResponse(
        code = 0,
        message = "登录成功",
        data = {
    
    
            "username": current_user.username,
            "realname": current_user.realname,
            "description": current_user.description
        }
    )
  • When logging out, log out the current user.
    When logging out, call role_required.logout(request)to log out the current user from the permission component. At this time, the cookie corresponding to the user in the component will be removed immediately, thus removing the user's login information.
@router.post("/logout/")
async def logout(
    request: Request,
    _: schemas.PyUser=Depends(role_required("管理员"))
):
    current_user = role_required.logout(request)
    return ApiResponse(
        code = 0,
        message = "登出成功",
        data = {
    
    
            "username": current_user.username,
            "realname": current_user.realname,
            "description": current_user.description
        }
    )

2. File structure

Insert image description here

Permission definition

If necessary, this part of the definition can be queried from the database, etc. Only the simplest definition is used here.
This part of the code is in the file roles.py.

roles = {
    
    
    "超级管理员": 1,
    "管理员": 2,
    "标注员": 3,
    "编辑者": 4,
    "审核员": 5,
    "游客": 6,
}

3. Permission component class

The core class of the permission component, the code is in require.pythe
three main functions:

  1. login() user login
  2. logout() user logout
  3. self() defines the permissions required for access
  • It should be noted that when I have insufficient permissions, I return an ApiExceptionerror status customized in this project. If necessary, I can change it myself.
  • The specific usage methods are as follows
from fastapi import HTTPException, Response, Request
import copy
from datetime import datetime, timedelta
import uuid
from starlette import status
from ..exception.apiexception import ApiResponse, ApiException

class RoleRequired:
    def __init__(
        self,
        guest,
        roles,
        redirect_url: str = None,
        expire_minutes: int = 30,
        clear_interval: int = 60,
    ):
        self.sessions = {
    
    }
        self.roles = roles
        self.guest = guest
        self.redirect_url = redirect_url
        self.expire_minutes = expire_minutes
        self.userclass = type(self.guest)
        self.last_clear_time = datetime.utcnow()
        self.interval = timedelta(minutes=clear_interval)


    def __clear_overstayed(self):
        now = datetime.utcnow()
        if (now - self.last_clear_time) < self.interval:
            return
        self.last_clear_time = now
        self.sessions = {
    
     k: v for k, v in self.sessions.items() if v['exp'] < now }


    def __create_token(self, response: Response, user=None):
        # print(self.sessions)
        self.__clear_overstayed()
        authorization = str(uuid.uuid1())
        response.set_cookie(key="authorization", value=authorization)
        if not user:
            user = copy.deepcopy(self.guest)
        self.sessions[authorization] = {
    
    
            "user": user,
            "exp": datetime.utcnow() + timedelta(minutes=self.expire_minutes)
        }
        return self.sessions[authorization]

    def __update_exp(self, authorization):
        exp = datetime.utcnow() + timedelta(minutes=self.expire_minutes)
        self.sessions[authorization]["exp"] = exp
        return self.sessions[authorization]

    def __verify_roles(self, user, roleids):
        if isinstance(roleids, list):
            return user.roleid in roleids
        return user.roleid == roleids


    def login(self, response: Response, user):
        # assert type(user) == type(self.guest)
        current_session = self.__create_token(response, user)
        return current_session['user']
    
    def logout(self, request: Request):
        authorization = request.cookies.get("authorization", None)
        try:
            current_session = self.sessions.pop(authorization)
            return current_session['user']
        except :
            raise ApiException(code=1005, message="当前未登录,登出发生错误")
        
    def __call__(self, *roles, **kwargs):
        login_only = kwargs.get("login_only", False)
        roles = [ self.roles[x] for x in roles ]
        if login_only:
            roles = list(self.roles.values())
        async def func(request: Request, response: Response) -> self.userclass:
            authorization = request.cookies.get("authorization", None)
            if not authorization or authorization not in self.sessions:
                current_session = self.__create_token(response)
            else:
                ntime = datetime.utcnow()
                session = self.sessions.get(authorization, None)
                if session['exp'] < ntime:
                    self.sessions.pop(authorization)
                    current_session = self.__create_token(response)
                else:
                    current_session = self.__update_exp(authorization)
            current_user = current_session['user']
            if not roles or self.__verify_roles(current_user, roles):
                return current_session['user']
            else:
                raise ApiException(
                    code=1004, message="权限不足"
                )
        return func

4. Instantiate permission class

The main parameters of this part of the code instantiated in __init__.pythe file in the permission package are as follows:

  • guest: When guests who are not logged in visit, they will be given a guest account by default.
  • roles: dictionary of permissions, which is roles.pythe dictionary defined in
  • redirect_url: str = None: The URL to jump to when you are not logged in or have no permissions (this part of the function is not implemented because the 1004 Insufficient Permissions status code is returned directly)
  • expire_minutes: int = 30: login status duration, expired cookies will no longer take effect
  • clear_interval: int = 60: When the time interval exceeds 60 minutes, if there is a request, the expired cookie will be cleared
from .require import RoleRequired
from ..models.schemas import  users as schemas
from .roles import roles
from app.settings import SESSION_DURATION

role_required = RoleRequired(guest=schemas.PyUser( userid=0, username="guest", password="guest", realname="guest", roleid=6 ),
                             roles=roles,
                             expire_minutes=SESSION_DURATION)

Guess you like

Origin blog.csdn.net/Defiler_Lee/article/details/118311784