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信息
出处:http://www.cnblogs.com/lightsong/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。