全栈框架权限管理实现
权限管理是大多数应用的核心需求,不同全栈框架的实现方式各有特点,下面介绍主流方案的实现思路。
通用权限管理模型
无论使用什么框架,底层模型通常相同:
用户(User) → 角色(Role) → 权限(Permission)
或者更精细的:
用户 → 角色 → 资源(Resource) + 操作(Action)
主流全栈框架实现
1 Next.js + Prisma + NextAuth.js
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
createdAt DateTime @default(now())
}
enum Role {
USER
ADMIN
SUPER_ADMIN
}
// 中间件实现权限检查
// middleware.ts
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth(
function middleware(req) {
const token = req.nextauth.token;
const path = req.nextUrl.pathname;
// 基于角色的路由保护
if (path.startsWith("/admin") && token?.role !== "ADMIN") {
return NextResponse.redirect(new URL("/403", req.url));
}
return NextResponse.next();
},
{
callbacks: {
authorized: ({ token }) => !!token
}
}
);
// API路由权限装饰器
// lib/permissions.ts
export function requireRole(role: Role) {
return async (req: NextApiRequest, res: NextApiResponse, next: Function) => {
const session = await getSession({ req });
if (!session || session.user.role !== role) {
return res.status(403).json({ error: "Forbidden" });
}
return next();
};
}
// pages/api/admin/users.ts
export default async function handler(req, res) {
await requireRole("ADMIN")(req, res, async () => {
// 只有管理员可以访问
const users = await prisma.user.findMany();
res.json(users);
});
}
2 Nuxt.js + Supabase/自定义后端
// plugins/auth.client.ts
export default defineNuxtPlugin(() => {
const { user } = useSupabaseUser();
// 添加权限检查组合函数
return {
provide: {
checkPermission: (requiredRole: string) => {
if (!user.value) return false;
return user.value.user_metadata?.role === requiredRole;
}
}
};
});
// 中间件保护页面
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { $checkPermission } = useNuxtApp();
if (to.path.startsWith('/admin') && !$checkPermission('admin')) {
return navigateTo('/login');
}
});
// 服务端API权限
// server/api/users/index.get.ts
import { serverSupabaseUser } from '#supabase/server';
export default defineEventHandler(async (event) => {
const user = await serverSupabaseUser(event);
if (!user || user.user_metadata?.role !== 'admin') {
throw createError({ statusCode: 403, message: 'Forbidden' });
}
// 业务逻辑
return { users: [...] };
});
3 Django + Django REST Framework
# permissions.py
from rest_framework.permissions import BasePermission
class IsAdmin(BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated and request.user.role == 'admin'
class IsOwnerOrAdmin(BasePermission):
def has_object_permission(self, request, view, obj):
return request.user == obj.owner or request.user.role == 'admin'
# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
class UserViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
if self.action in ['list', 'destroy']:
permission_classes = [IsAdmin]
elif self.action == 'retrieve':
permission_classes = [IsAdmin | IsOwner]
else:
permission_classes = [AllowAny]
return [p() for p in permission_classes]
@action(detail=True, methods=['post'], permission_classes=[IsAdmin])
def promote_to_admin(self, request, pk=None):
user = self.get_object()
user.role = 'admin'
user.save()
return Response({'status': 'user promoted'})
4 Spring Boot + Spring Security + JPA
// SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
}
// PermissionEvaluator实现
@Service
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
if (auth == null || !(targetDomainObject instanceof Resource)) {
return false;
}
// 检查用户是否有指定资源的特定权限
User user = (User) auth.getPrincipal();
Resource resource = (Resource) targetDomainObject;
return user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.anyMatch(p -> p.getResource().equals(resource) && p.getAction().equals(permission));
}
}
// 方法级注解
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
@PreAuthorize("hasPermission(#id, 'User', 'READ')")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public void deleteUser(@PathVariable Long id) {
userService.deleteById(id);
}
}
前端权限控制
1 Vue Router 导航守卫
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/admin',
component: AdminLayout,
meta: { requiresAuth: true, role: 'admin' },
children: [
{ path: 'users', component: UserManagement, meta: { permissions: ['user:read', 'user:write'] } },
{ path: 'settings', component: Settings, meta: { permissions: ['settings:manage'] } }
]
}
];
const router = createRouter({ history: createWebHistory(), routes });
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
return next('/login');
}
if (to.meta.role && !authStore.hasRole(to.meta.role)) {
return next('/403');
}
if (to.meta.permissions) {
const hasAllPermissions = to.meta.permissions.every(
perm => authStore.hasPermission(perm)
);
if (!hasAllPermissions) return next('/403');
}
next();
});
2 React 路由保护组件
// components/ProtectedRoute.tsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
interface ProtectedRouteProps {
children: React.ReactNode;
requiredRoles?: string[];
requiredPermissions?: string[];
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
requiredRoles = [],
requiredPermissions = []
}) => {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <LoadingSpinner />;
if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
const hasRole = requiredRoles.length === 0 ||
requiredRoles.some(role => user.roles.includes(role));
const hasPermission = requiredPermissions.length === 0 ||
requiredPermissions.every(perm => user.permissions.includes(perm));
if (!hasRole || !hasPermission) {
return <Navigate to="/403" replace />;
}
return <>{children}</>;
};
// App.tsx
<Routes>
<Route path="/admin" element={
<ProtectedRoute requiredRoles={['admin']}>
<AdminPanel />
</ProtectedRoute>
} />
<Route path="/dashboard" element={
<ProtectedRoute requiredPermissions={['dashboard:view']}>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
最佳实践建议
1 权限设计原则
- 最小权限原则:用户只获得完成工作所需的最小权限
- 权限分层:权限、角色、用户组三层结构易于管理
- 前端只是辅助:前端权限控制仅用于UI展示,后端必须验证所有权限
2 存储方式选择
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JWT Token存储 | 无状态,扩展性好 | Token大小限制,无法实时撤销 | 微服务架构 |
| 服务端Session | 可实时撤销权限 | 有状态,需要共享session | 单体应用 |
| 数据库查询 | 灵活,支持复杂权限 | 每次请求查询,性能较差 | 权限粒度很细的系统 |
3 ABAC vs RBAC
// RBAC (基于角色的访问控制)
if (user.role === 'admin') {
// 允许访问
}
// ABAC (基于属性的访问控制)
function canAccessResource(user, resource, action, environment) {
const rules = [
{ effect: 'deny', condition: user.department !== resource.department && action === 'write' },
{ effect: 'allow', condition: user.role === 'manager' || user.id === resource.owner },
{ effect: 'deny', condition: environment.time > '18:00' && action === 'delete' }
];
for (const rule of rules) {
if (rule.condition) return rule.effect === 'allow';
}
return false;
}
4 性能优化
- 权限缓存:使用Redis缓存用户权限,减少数据库查询
- 批量检查:前端请求时一次性返回需要的权限列表
- 预加载:页面加载时提前获取权限状态
安全注意事项
- 永远不要信任前端传递的权限信息
- 所有API端点都在后端进行权限校验
- 使用HTTP-only Cookie存储认证token
- 实施CSRF防护
- 记录权限审计日志
选择具体实现时,需要根据项目的规模、团队技术栈和权限复杂度来决定,对于简单应用,RBAC通常足够;对于复杂的多租户系统,建议使用ABAC或两者的结合。
标签: 权限管理