You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring通用CRUD控制器添加安全权限控制方案问询

Hey, great question! I’ve dealt with exactly this scenario before—having a generic CRUD controller and wanting to avoid writing those repetitive override methods just to add @PreAuthorize. Here are three solid approaches that will let you centralize your permission checks without touching every subclass:

Approach 1: Custom Annotation + Spring AOP

This is super flexible, especially since you have a consistent permission naming pattern (like XXX_LIST, XXX_ADD).

  1. First, create a custom annotation to mark CRUD methods and specify the operation type:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireCrudPermission {
    String operation(); // Values: ADD, LIST, UPDATE, DELETE
}
  1. Update your generic parent controller to use this annotation instead of @PreAuthorize, and add an abstract method to get the permission prefix for each entity:
public abstract class GenericCrudController<T> {

    // Subclasses will return their specific prefix, e.g., "CATEGORY" or "PROJECT"
    protected abstract String getPermissionPrefix();

    @RequireCrudPermission(operation = "ADD")
    @PostMapping("")
    public ResponseEntity<Object> create(@Valid @RequestBody T m, BindingResult bindingResult) throws CustomValidateException {
        // Your generic create logic here
    }

    @RequireCrudPermission(operation = "LIST")
    @GetMapping("")
    public ResponseEntity<List<T>> list() {
        // Your generic list logic here
    }

    // Repeat for UPDATE/DELETE methods
}
  1. Build an AOP aspect to intercept methods with @RequireCrudPermission, dynamically build the full permission string, and validate access:
@Aspect
@Component
public class CrudPermissionAspect {

    @Autowired
    private SecurityContextHolderStrategy securityContextHolderStrategy;

    @Around("@annotation(requirePermission)")
    public Object checkCrudPermission(ProceedingJoinPoint joinPoint, RequireCrudPermission requirePermission) throws Throwable {
        // Get the current controller instance
        GenericCrudController<?> controller = (GenericCrudController<?>) joinPoint.getTarget();
        // Build full permission (e.g., CATEGORY_ADD)
        String requiredPermission = controller.getPermissionPrefix() + "_" + requirePermission.operation();

        // Validate permission matching Spring Security's hasAuthority logic
        Authentication auth = securityContextHolderStrategy.getContext().getAuthentication();
        if (auth == null || !auth.getAuthorities().stream()
                .anyMatch(authority -> authority.getAuthority().equals(requiredPermission))) {
            throw new AccessDeniedException("Missing required permission: " + requiredPermission);
        }

        return joinPoint.proceed();
    }
}
  1. Now your subcontrollers only need to implement the prefix method—no more redundant overrides!
@RestController
@RequestMapping("/categories")
public class CategoryController extends GenericCrudController<Category> {

    @Override
    protected String getPermissionPrefix() {
        return "CATEGORY";
    }

    // No more copy-pasted create/list methods!
}

Approach 2: Dynamic SpEL Expressions in @PreAuthorize

If you prefer to stick with Spring Security’s native annotations, you can use SpEL to dynamically generate the permission string directly in the parent controller.

  1. Add the abstract prefix method to your generic controller, then use #this in SpEL to reference the current controller instance:
public abstract class GenericCrudController<T> {

    protected abstract String getPermissionPrefix();

    @PreAuthorize("hasAuthority(#this.getPermissionPrefix() + '_ADD')")
    @PostMapping("")
    public ResponseEntity<Object> create(@Valid @RequestBody T m, BindingResult bindingResult) throws CustomValidateException {
        // Generic create logic
    }

    @PreAuthorize("hasAuthority(#this.getPermissionPrefix() + '_LIST')")
    @GetMapping("")
    public ResponseEntity<List<T>> list() {
        // Generic list logic
    }
}
  1. Subcontrollers just need to implement the prefix method, same as Approach 1:
@RestController
@RequestMapping("/projects")
public class ProjectController extends GenericCrudController<Project> {

    @Override
    protected String getPermissionPrefix() {
        return "PROJECT";
    }
}

The key here is that #this in SpEL refers to the current controller instance, letting you call its getPermissionPrefix() method to build the permission string dynamically.

Approach 3: Auto-Infer Prefix via Reflection (Zero Subclass Code)

If your entity class names match your permission prefixes (e.g., Category → CATEGORY), you can skip the prefix method entirely by using reflection to auto-infer the prefix from the generic type:

public abstract class GenericCrudController<T> {

    private final String permissionPrefix;

    // Infer prefix from the generic entity class name (e.g., Category → CATEGORY)
    public GenericCrudController() {
        Type superType = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) superType;
        Class<T> entityClass = (Class<T>) paramType.getActualTypeArguments()[0];
        this.permissionPrefix = entityClass.getSimpleName().toUpperCase();
    }

    @PreAuthorize("hasAuthority(#this.permissionPrefix + '_ADD')")
    @PostMapping("")
    public ResponseEntity<Object> create(@Valid @RequestBody T m, BindingResult bindingResult) throws CustomValidateException {
        // Generic create logic
    }

    @PreAuthorize("hasAuthority(#this.permissionPrefix + '_LIST')")
    @GetMapping("")
    public ResponseEntity<List<T>> list() {
        // Generic list logic
    }
}

Now your subcontrollers don’t need any extra code at all—just extend the generic controller and you’re done! If you have exceptions to the naming rule, you can add a fallback (like an optional annotation on the entity to specify the prefix).

Quick Notes

  • Approach 1 (AOP) is best if you need extra logic around permission checks (like logging) or have complex rules.
  • Approach 2 is the simplest if you want to stay within Spring Security’s native features.
  • Approach 3 is the most concise, but relies on consistent naming between entities and permissions.

All these approaches let you keep your existing codebase intact—no need to rewrite controllers or switch REST implementations.

内容的提问来源于stack exchange,提问作者Deme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:51:12