Stay Hungry,Stay Foolish!

based on keycloak, manage user resource permission

Assign permission to user through role and group

https://www.keycloak.org/docs/latest/server_admin/#assigning-permissions-using-roles-and-groups

Code:

https://github.com/fanqingsong/fastapi-react-postgres-keycloak-sso

 

Authentication

只提供认证

async def verify_token(token: str = Depends(oauth2_scheme)) -> tp.Dict[str, str]:
    """Verify token with Keycloak public key.

    Args:
        token: access token to decode

    Returns:
        Token decoded
    """
    try:
        return keycloak_openid.decode_token(
            token,
            key=KEYCLOAK_PUBLIC_KEY,
            options={"verify_signature": True, "verify_aud": False, "exp": True},
        )
    except (KeycloakGetError, JWTError) as error:
        raise HTTPException(
            status_code=401, detail=str(error), headers={"WWW-Authenticate": "Bearer"}
        ) from error

 

Authorization

不仅仅提供认证,还提供权限permission控制。

基于keycloak的client的role。

def verify_permission(required_roles=[]):

    async def verify_token(token: str = Depends(oauth2_scheme)) -> tp.Dict[str, str]:
        """Verify token with Keycloak public key.

        Args:
            token: access token to decode

        Returns:
            Token decoded
        """
        try:
            token_info = keycloak_openid.decode_token(
                token,
                key=KEYCLOAK_PUBLIC_KEY,
                options={"verify_signature": True, "verify_aud": False, "exp": True},
            )

            print("---------------------------------------")
            print(token_info)

            resource_access = token_info['resource_access']
            app_name = os.environ.get('KEYCLOAK_CLIENT_ID')
            app_property = resource_access[app_name] if app_name in resource_access else {}
            user_roles = app_property['roles'] if 'roles' in app_property else []

            for role in required_roles:
                if role not in user_roles:
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail=f'Role "{role}" is required to perform this action',
                    )

            return token_info
        except (KeycloakGetError, JWTError) as error:
            raise HTTPException(
                status_code=401, detail=str(error), headers={"WWW-Authenticate": "Bearer"}
            ) from error

    return verify_token

 

APP usage

第一个仅仅提供authentication

二和三提供认证+权限控制

@app.get("/user")  # Requires logged in
def current_users(user = Depends(verify_token)):
    return user


@app.get("/admin")  # Requires the admin role
def company_admin(user = Depends(verify_permission(required_roles=["admin"]))):
    return f'Hi admin {user}'


@app.get("/protected", dependencies=[Depends(verify_permission(required_roles=["admin"]))])  # Requires the admin role
def company_admin():
    return f'Hi, this is protected path'

 

 

参考

https://fastapi-keycloak.code-specialist.com/quick_start/

@app.get("/admin")  # Requires the admin role
def company_admin(user: OIDCUser = Depends(idp.get_current_user(required_roles=["admin"]))):
    return f'Hi admin {user}'

 

https://github.com/code-specialist/fastapi-keycloak/blob/master/fastapi_keycloak/api.py

    def get_current_user(self, required_roles: List[str] = None, extra_fields: List[str] = None) -> Callable[OAuth2PasswordBearer, OIDCUser]:
        """Returns the current user based on an access token in the HTTP-header. Optionally verifies roles are possessed
        by the user

        Args:
            required_roles List[str]: List of role names required for this endpoint
            extra_fields List[str]: The names of the additional fields you need that are encoded in JWT

        Returns:
            Callable[OAuth2PasswordBearer, OIDCUser]: Dependency method which returns the decoded JWT content

        Raises:
            ExpiredSignatureError: If the token is expired (exp > datetime.now())
            JWTError: If decoding fails or the signature is invalid
            JWTClaimsError: If any claim is invalid
            HTTPException: If any role required is not contained within the roles of the users
        """

        def current_user(
                token: OAuth2PasswordBearer = Depends(self.user_auth_scheme),
        ) -> OIDCUser:
            """Decodes and verifies a JWT to get the current user

            Args:
                token OAuth2PasswordBearer: Access token in `Authorization` HTTP-header

            Returns:
                OIDCUser: Decoded JWT content

            Raises:
                ExpiredSignatureError: If the token is expired (exp > datetime.now())
                JWTError: If decoding fails or the signature is invalid
                JWTClaimsError: If any claim is invalid
                HTTPException: If any role required is not contained within the roles of the users
            """
            decoded_token = self._decode_token(token=token, audience="account")
            user = OIDCUser.parse_obj(decoded_token)
            if required_roles:
                for role in required_roles:
                    if role not in user.roles:
                        raise HTTPException(
                            status_code=status.HTTP_403_FORBIDDEN,
                            detail=f'Role "{role}" is required to perform this action',
                        )

            if extra_fields:
                for field in extra_fields:
                    user.extra_fields[field] = decoded_token.get(field, None)

            return user

        return current_user

 

https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/

两种依赖方式

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons

 

https://python-keycloak.readthedocs.io/en/latest/readme.html#documentation

关于角色获取,以及其它接口后续可用。

# Retrieve client roles of a user.
keycloak_admin.get_client_roles_of_user(user_id="user_id", client_id="client_id")

# Retrieve available client roles of a user.
keycloak_admin.get_available_client_roles_of_user(user_id="user_id", client_id="client_id")

 

https://blog.csdn.net/zhubozhen/article/details/115412344

从token中可以获取用户拥有的 roles信息

 

posted @ 2023-05-16 10:53  lightsong  阅读(95)  评论(0编辑  收藏  举报
Life Is Short, We Need Ship To Travel