feat: api visit auth
This commit is contained in:
parent
fc1acab2d5
commit
9ce271d691
@ -4,15 +4,10 @@ from fastapi import Query
|
||||
|
||||
from core.utils import list_to_tree
|
||||
from dbhelper.relation import role_assigned_menu
|
||||
from dbhelper.role import (
|
||||
del_role,
|
||||
get_role,
|
||||
get_role_menus,
|
||||
get_roles,
|
||||
new_role,
|
||||
put_role,
|
||||
)
|
||||
from schemas import ListAll, Response, RoleIn, RoleInfo, RoleMenuIn, RoleQuery, RoleRead
|
||||
from dbhelper.role import (del_role, get_role, get_role_menus, get_roles,
|
||||
new_role, put_role)
|
||||
from schemas import (ListAll, Response, RoleIn, RoleInfo, RoleMenuIn,
|
||||
RoleQuery, RoleRead)
|
||||
|
||||
|
||||
async def role_add(data: RoleIn) -> Response[RoleInfo]:
|
||||
|
@ -1,14 +1,8 @@
|
||||
from fastapi import Query
|
||||
|
||||
from core.security import get_password_hash
|
||||
from dbhelper.user import (
|
||||
del_user,
|
||||
get_user,
|
||||
get_user_info,
|
||||
get_users,
|
||||
insert_user,
|
||||
put_user,
|
||||
)
|
||||
from dbhelper.user import (del_user, get_user, get_user_info, get_users,
|
||||
insert_user, put_user)
|
||||
from schemas import Response, UserAdd, UserInfo, UserPut, UserQuery, UserRead
|
||||
from schemas.common import ListAll
|
||||
|
||||
|
@ -1,6 +1,23 @@
|
||||
from fastapi.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class TokenAuthFailure(HTTPException):
|
||||
status_code = 401
|
||||
detail = "认证失败"
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PermissionsError(HTTPException):
|
||||
pass
|
||||
|
||||
|
||||
async def http_exception(request: Request, exc: HTTPException):
|
||||
return JSONResponse(
|
||||
{"msg": exc.detail, "code": exc.status_code, "data": None},
|
||||
status_code=exc.status_code,
|
||||
headers=exc.headers,
|
||||
)
|
||||
|
||||
|
||||
exception_handlers = {HTTPException: http_exception}
|
||||
|
@ -1,13 +1,15 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from core.exceptions import TokenAuthFailure
|
||||
from dbhelper.user import get_user
|
||||
from core.exceptions import PermissionsError, TokenAuthFailure
|
||||
from dbhelper.menu import get_apis, get_has_api
|
||||
from dbhelper.user import get_user, get_user_info
|
||||
from models import UserModel
|
||||
|
||||
# JWT
|
||||
SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU"
|
||||
@ -59,4 +61,33 @@ async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)):
|
||||
username: str = payload.get("sub")
|
||||
return await get_user({"username": username})
|
||||
except JWTError:
|
||||
raise TokenAuthFailure
|
||||
raise TokenAuthFailure(403, "认证失败")
|
||||
|
||||
|
||||
async def check_permissions(request: Request, user: UserModel = Depends(check_token)):
|
||||
"""检查接口权限"""
|
||||
# 查询当前激活角色
|
||||
result = await get_user_info(user)
|
||||
active_rid = result["roles"][0]["id"]
|
||||
|
||||
# 白名单
|
||||
whitelist = [f"/user/{user.id}", f"/role/{active_rid}/menu"]
|
||||
flag = request.url.path in whitelist and request.method == "GET"
|
||||
if flag:
|
||||
return
|
||||
|
||||
api = request.url.path
|
||||
for k, v in request.path_params.items():
|
||||
api = api.replace(v, "{%s}" % k)
|
||||
# 方法1. 每一次去查数据库
|
||||
# result = await get_has_api(active_rid, api, request.method)
|
||||
|
||||
# 2. 登录之后查一次 后面去结果查 todo 更新权限时需要更新 , 最好结果放redis
|
||||
cache_key = f"{user.username}_{active_rid}"
|
||||
# 缓存到fastapi 应用实例中
|
||||
if not hasattr(request.app.state, cache_key):
|
||||
setattr(request.app.state, cache_key, await get_apis(active_rid))
|
||||
if {"api": api, "method": request.method} not in getattr(
|
||||
request.app.state, cache_key
|
||||
):
|
||||
raise PermissionsError(403, detail="无权访问")
|
||||
|
@ -1,3 +1,5 @@
|
||||
from tortoise import connections
|
||||
|
||||
from models import MenuModel
|
||||
from schemas.menu import MenuIn
|
||||
|
||||
@ -41,3 +43,27 @@ async def get_menu(kwargs):
|
||||
async def del_menu(mid: int):
|
||||
"""删除用户"""
|
||||
return await MenuModel.filter(id=mid).update(status=9)
|
||||
|
||||
|
||||
async def get_has_api(pk: int, api: str, method: str):
|
||||
"""获取角色接口权限 每次来查数据库"""
|
||||
db = connections.get("default")
|
||||
return await db.execute_query_dict(
|
||||
"""
|
||||
select m.api, m.method
|
||||
FROM sys_menu as m, sys_role_menu as srm WHERE m.id = srm.mid
|
||||
AND srm.rid = (?) and m.api = (?) and m.method = (?) and m.status != 9""",
|
||||
[pk, api, method],
|
||||
)
|
||||
|
||||
|
||||
async def get_apis(pk: int):
|
||||
"""返回当前角色拥有的接口权限列表"""
|
||||
db = connections.get("default")
|
||||
return await db.execute_query_dict(
|
||||
"""
|
||||
select m.api, m.method
|
||||
FROM sys_menu as m, sys_role_menu as srm WHERE m.id = srm.mid
|
||||
AND srm.rid = (?) and m.status != 9""",
|
||||
[pk],
|
||||
)
|
||||
|
@ -11,7 +11,7 @@ async def get_role_menus(rid: int):
|
||||
db = connections.get("default")
|
||||
return await db.execute_query_dict(
|
||||
"""
|
||||
select m.id, m.name, m.meta, m.path, m.type, m.component, m.pid, m.identifier, m.regx,m.api, m.method
|
||||
select m.id, m.name, m.meta, m.path, m.type, m.component, m.pid, m.identifier
|
||||
FROM sys_menu as m, sys_role_menu WHERE m.id = sys_role_menu.mid
|
||||
AND sys_role_menu.rid = (?) AND m.`status` = 1""",
|
||||
[rid],
|
||||
|
@ -1,6 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from core.events import close_orm, init_orm
|
||||
from core.exceptions import exception_handlers
|
||||
from core.log import logger
|
||||
from core.middleware import middlewares
|
||||
from router.url import routes
|
||||
@ -10,14 +11,13 @@ app = FastAPI(
|
||||
on_shutdown=[close_orm],
|
||||
routes=routes,
|
||||
middleware=middlewares,
|
||||
exception_handlers=exception_handlers,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
for i in app.routes:
|
||||
logger.info(
|
||||
f"{i.path}, {i.methods}, {i.path_regex}, {i.__dict__.get('summary')}, {i.endpoint}"
|
||||
)
|
||||
logger.info(f"{i.path}, {i.methods}, {i.__dict__.get('summary')}, {i.endpoint}")
|
||||
|
||||
uvicorn.run("main:app", reload=True)
|
||||
|
Binary file not shown.
Binary file not shown.
@ -15,7 +15,6 @@ class MenuModel(Table):
|
||||
identifier = fields.CharField(max_length=30, description="权限标识 user:add", null=True)
|
||||
api = fields.CharField(max_length=128, description="接口地址", null=True)
|
||||
method = fields.CharField(max_length=10, description="接口请求方式", null=True)
|
||||
regx = fields.CharField(max_length=50, description="接口地址正则表达式", null=True)
|
||||
|
||||
class Meta:
|
||||
table = "sys_menu"
|
||||
|
@ -4,17 +4,11 @@ from fastapi import Depends, routing
|
||||
|
||||
from controller.common import about, login
|
||||
from controller.menu import menu_add, menu_arr, menu_del
|
||||
from controller.role import (
|
||||
assigned_menu,
|
||||
role_add,
|
||||
role_arr,
|
||||
role_del,
|
||||
role_has_menu,
|
||||
role_put,
|
||||
role_query,
|
||||
)
|
||||
from controller.user import user_add, user_arr, user_del, user_info, user_list, user_put
|
||||
from core.security import check_token
|
||||
from controller.role import (assigned_menu, role_add, role_arr, role_del,
|
||||
role_has_menu, role_put, role_query)
|
||||
from controller.user import (user_add, user_arr, user_del, user_info,
|
||||
user_list, user_put)
|
||||
from core.security import check_permissions
|
||||
|
||||
|
||||
class Route(routing.APIRoute):
|
||||
@ -113,48 +107,55 @@ class Route(routing.APIRoute):
|
||||
)
|
||||
|
||||
|
||||
has_perm = {"dependencies": [Depends(check_permissions)]}
|
||||
|
||||
routes = [
|
||||
Route.post("/login", endpoint=login, tags=["公共"], summary="登录"),
|
||||
Route.get("/about", endpoint=about, tags=["公共"], summary="关于"),
|
||||
Route.get("/about", endpoint=about, tags=["公共"], summary="关于", **has_perm),
|
||||
# 用户管理
|
||||
Route.get("/user", endpoint=user_arr, tags=["用户管理"], summary="用户列表"),
|
||||
Route.post("/user", endpoint=user_add, tags=["用户管理"], summary="用户新增"),
|
||||
Route.get("/user", endpoint=user_arr, tags=["用户管理"], summary="用户列表", **has_perm),
|
||||
Route.post("/user", endpoint=user_add, tags=["用户管理"], summary="用户新增", **has_perm),
|
||||
Route.delete(
|
||||
"/user/{pk}",
|
||||
endpoint=user_del,
|
||||
tags=["用户管理"],
|
||||
summary="用户删除",
|
||||
"/user/{pk}", endpoint=user_del, tags=["用户管理"], summary="用户删除", **has_perm
|
||||
),
|
||||
Route.put("/user/{pk}", endpoint=user_put, tags=["用户管理"], summary="用户更新"),
|
||||
Route.get("/user/{pk}", endpoint=user_info, tags=["用户管理"], summary="用户信息"),
|
||||
Route.post("/user/query", endpoint=user_list, tags=["用户管理"], summary="用户列表查询"),
|
||||
# 角色管理,
|
||||
Route.get("/role", endpoint=role_arr, tags=["角色管理"], summary="角色列表"),
|
||||
Route.post("/role", endpoint=role_add, tags=["角色管理"], summary="角色新增"),
|
||||
Route.delete(
|
||||
"/role/{pk}",
|
||||
endpoint=role_del,
|
||||
tags=["角色管理"],
|
||||
summary="角色删除",
|
||||
dependencies=[Depends(check_token)],
|
||||
Route.put(
|
||||
"/user/{pk}", endpoint=user_put, tags=["用户管理"], summary="用户更新", **has_perm
|
||||
),
|
||||
Route.get(
|
||||
"/role/{rid}/menu", endpoint=role_has_menu, tags=["角色管理"], summary="查询角色拥有权限"
|
||||
"/user/{pk}", endpoint=user_info, tags=["用户管理"], summary="用户信息", **has_perm
|
||||
),
|
||||
Route.put("/role", endpoint=role_put, tags=["角色管理"], summary="角色更新"),
|
||||
Route.post("/role/query", endpoint=role_query, tags=["角色管理"], summary="角色条件查询"),
|
||||
Route.post(
|
||||
"/role/assigned/menu", endpoint=assigned_menu, tags=["角色管理"], summary="角色分配菜单"
|
||||
"/user/query", endpoint=user_list, tags=["用户管理"], summary="用户列表查询", **has_perm
|
||||
),
|
||||
# 角色管理,
|
||||
Route.get("/role", endpoint=role_arr, tags=["角色管理"], summary="角色列表", **has_perm),
|
||||
Route.post("/role", endpoint=role_add, tags=["角色管理"], summary="角色新增", **has_perm),
|
||||
Route.delete(
|
||||
"/role/{pk}", endpoint=role_del, tags=["角色管理"], summary="角色删除", **has_perm
|
||||
),
|
||||
Route.get(
|
||||
"/role/{rid}/menu",
|
||||
endpoint=role_has_menu,
|
||||
tags=["角色管理"],
|
||||
summary="查询角色拥有权限",
|
||||
**has_perm
|
||||
),
|
||||
Route.put("/role", endpoint=role_put, tags=["角色管理"], summary="角色更新", **has_perm),
|
||||
Route.post(
|
||||
"/role/query", endpoint=role_query, tags=["角色管理"], summary="角色条件查询", **has_perm
|
||||
),
|
||||
Route.post(
|
||||
"/role/assigned/menu",
|
||||
endpoint=assigned_menu,
|
||||
tags=["角色管理"],
|
||||
summary="角色分配菜单",
|
||||
**has_perm
|
||||
),
|
||||
# 菜单新增
|
||||
Route.get("/menu", endpoint=menu_arr, tags=["菜单管理"], summary="菜单列表"),
|
||||
Route.post("/menu", endpoint=menu_add, tags=["菜单管理"], summary="菜单新增"),
|
||||
Route.get("/menu", endpoint=menu_arr, tags=["菜单管理"], summary="菜单列表", **has_perm),
|
||||
Route.post("/menu", endpoint=menu_add, tags=["菜单管理"], summary="菜单新增", **has_perm),
|
||||
Route.delete(
|
||||
"/menu/{pk}",
|
||||
endpoint=menu_del,
|
||||
tags=["菜单管理"],
|
||||
summary="菜单删除",
|
||||
dependencies=[Depends(check_token)],
|
||||
"/menu/{pk}", endpoint=menu_del, tags=["菜单管理"], summary="菜单删除", **has_perm
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -15,7 +15,6 @@ class MenuBasic(BaseModel):
|
||||
identifier: Optional[str] = Field(default=None, description="权限标识符 -> 按钮显示")
|
||||
api: Optional[str] = Field(default=None, description="后端接口地址")
|
||||
method: Optional[str] = Field(default=None, description="接口请求方法")
|
||||
regx: Optional[str] = Field(default=None, description="正则匹配")
|
||||
|
||||
|
||||
class MenuIn(MenuBasic):
|
||||
|
@ -51,7 +51,6 @@ params = [
|
||||
identifier=None,
|
||||
api=None,
|
||||
method=None,
|
||||
regx=None,
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -66,7 +65,6 @@ params = [
|
||||
identifier=None,
|
||||
api=None,
|
||||
method=None,
|
||||
regx=None,
|
||||
).dict(),
|
||||
),
|
||||
# 组件
|
||||
@ -81,8 +79,7 @@ params = [
|
||||
pid=1,
|
||||
identifier=None,
|
||||
api="/user",
|
||||
method="{'GET'}",
|
||||
regx="^/user$",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -96,8 +93,7 @@ params = [
|
||||
pid=1,
|
||||
identifier=None,
|
||||
api="/role",
|
||||
method="{'GET'}",
|
||||
regx="^/role$",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -111,8 +107,7 @@ params = [
|
||||
pid=1,
|
||||
identifier=None,
|
||||
api="/menu",
|
||||
method="{'GET'}",
|
||||
regx="^/menu$",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -126,8 +121,7 @@ params = [
|
||||
pid=2,
|
||||
identifier=None,
|
||||
api="/about",
|
||||
method="{'GET'}",
|
||||
regx="^/about",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
# 按钮
|
||||
@ -142,8 +136,7 @@ params = [
|
||||
pid=3,
|
||||
identifier="user:create",
|
||||
api="/user",
|
||||
method="{'POST'}",
|
||||
regx="^/user$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -157,8 +150,7 @@ params = [
|
||||
pid=3,
|
||||
identifier="user:delete",
|
||||
api="/user/{pk}",
|
||||
method="{'DELETE'}",
|
||||
regx="^/user/(?P<pk>[^/]+)$",
|
||||
method="DELETE",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -172,8 +164,7 @@ params = [
|
||||
pid=3,
|
||||
identifier="user:update",
|
||||
api="/user/{pk}",
|
||||
method="{'PUT'}",
|
||||
regx="^/user/(?P<pk>[^/]+)$",
|
||||
method="PUT",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -187,8 +178,7 @@ params = [
|
||||
pid=3,
|
||||
identifier="user:get",
|
||||
api="/user/{pk}",
|
||||
method="{'GET'}",
|
||||
regx="^/user/(?P<pk>[^/]+)$",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -202,8 +192,7 @@ params = [
|
||||
pid=3,
|
||||
identifier="user:query",
|
||||
api="/user/query",
|
||||
method="{'POST'}",
|
||||
regx="^/user/query$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
# 角色管理
|
||||
@ -218,8 +207,7 @@ params = [
|
||||
pid=4,
|
||||
identifier="role:create",
|
||||
api="/role",
|
||||
method="{'POST'}",
|
||||
regx="^/role$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -233,8 +221,7 @@ params = [
|
||||
pid=4,
|
||||
identifier="role:delete",
|
||||
api="/role/{pk}",
|
||||
method="{'DELETE'}",
|
||||
regx="^/role/(?P<pk>[^/]+)$",
|
||||
method="DELETE",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -248,8 +235,7 @@ params = [
|
||||
pid=4,
|
||||
identifier=None,
|
||||
api="/role/{rid}/menu",
|
||||
method="{'GET'}",
|
||||
regx="^/role/(?P<rid>[^/]+)/menu$",
|
||||
method="GET",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -263,8 +249,7 @@ params = [
|
||||
pid=4,
|
||||
identifier="",
|
||||
api="/role/query",
|
||||
method="{'POST'}",
|
||||
regx="^/role/query$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -278,8 +263,7 @@ params = [
|
||||
pid=4,
|
||||
identifier="role:assign",
|
||||
api="/role/assigned/menu",
|
||||
method="{'POST'}",
|
||||
regx="^/role/assigned/menu$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -293,8 +277,7 @@ params = [
|
||||
pid=4,
|
||||
identifier="role:update",
|
||||
api="/role",
|
||||
method="{'PUT'}",
|
||||
regx="^/role$",
|
||||
method="PUT",
|
||||
).dict(),
|
||||
),
|
||||
# 菜单管理的权限
|
||||
@ -309,8 +292,7 @@ params = [
|
||||
pid=5,
|
||||
identifier="menu:create",
|
||||
api="/menu",
|
||||
method="{'POST'}",
|
||||
regx="^/menu$",
|
||||
method="POST",
|
||||
).dict(),
|
||||
),
|
||||
(
|
||||
@ -324,8 +306,7 @@ params = [
|
||||
pid=5,
|
||||
identifier="menu:delete",
|
||||
api="/menu/{pk}",
|
||||
method="{'DELETE'}",
|
||||
regx="/menu/(?P<pk>[^/]+)$",
|
||||
method="DELETE",
|
||||
).dict(),
|
||||
),
|
||||
# 分配权限
|
||||
@ -333,7 +314,7 @@ params = [
|
||||
"/role/assigned/menu",
|
||||
RoleMenuIn(rid=1, menus=[num for num in range(1, 20)]).dict(),
|
||||
),
|
||||
("/role/assigned/menu", RoleMenuIn(rid=2, menus=[3, 7, 8, 9, 10, 11]).dict()),
|
||||
("/role/assigned/menu", RoleMenuIn(rid=2, menus=[1, 3, 7, 8, 9, 11]).dict()),
|
||||
]
|
||||
|
||||
|
||||
|
@ -12,6 +12,14 @@ export const userStore = defineStore('user', () => {
|
||||
// getter
|
||||
const accessToken = computed(() => 'Bearer ' + token.value)
|
||||
|
||||
// setup store 不提供$reset 需要自己重置
|
||||
// https://github.com/vuejs/pinia/issues/1056
|
||||
const $reset = () => {
|
||||
token.value = ""
|
||||
userInfo.value = {}
|
||||
userMenus.value = []
|
||||
}
|
||||
|
||||
// 非setup语法时的actions
|
||||
const loginAction = async (data) => {
|
||||
|
||||
@ -34,7 +42,8 @@ export const userStore = defineStore('user', () => {
|
||||
ElMessage.success("登录成功.")
|
||||
}
|
||||
|
||||
return { token, accessToken, userInfo, userMenus, loginAction }
|
||||
return { token, accessToken, userInfo, userMenus,
|
||||
$reset, loginAction }
|
||||
}, {
|
||||
persist: true, // 解决pinia刷新时数据丢失问题
|
||||
})
|
||||
|
@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import router from '@/router';
|
||||
import { userStore } from '@/stores/user';
|
||||
const store = userStore()
|
||||
|
||||
const logout = () => {
|
||||
store.$reset()
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -7,7 +14,9 @@
|
||||
<el-container>
|
||||
<el-aside width="200px">Aside</el-aside>
|
||||
<el-container>
|
||||
<el-header>Header</el-header>
|
||||
<el-header>Header <el-button @click="logout">
|
||||
注销
|
||||
</el-button></el-header>
|
||||
<el-main>Main</el-main>
|
||||
<el-footer>Footer</el-footer>
|
||||
</el-container>
|
||||
|
Loading…
x
Reference in New Issue
Block a user