公司想把基本的CRUD接口抽取到 BaseController ,因每个 Controller 的 Spring Security 的@PreAuthorize注解的权限字符串不同,所以不能使用这个,只能另寻他法
个人不太喜欢这种抽取
控制器中的接口类型枚举
public enum ApiInterfaceType {
/**
* 新增
*/
ADD,
/**
* 更新
*/
UPDATE,
/**
* 删除
*/
DELETE,
}
自定义动态权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicPreAuthorize {
ApiInterfaceType interfaceType();
}
BaseController 的 公共方法加上注解
@DynamicPreAuthorize(interfaceType = ApiInterfaceType.ADD)
@PostMapping("/add")
public Boolean add(@RequestBody @Validated T entity) {
return baseService.save(entity);
}
定义用于在控制器类上指定接口类型和对应的权限的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ControllerPermissions {
PermissionMapping[] value();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionMapping {
ApiInterfaceType interfaceType();
String[] permissions();
}
在继承 BaseController 的 UserController 中加上权限映射注解
@ControllerPermissions({
@PermissionMapping(interfaceType = ADD, permissions = {"user_admin", "user_edit"})
})
@RequestMapping("/user")
@RestController
public class UserController extends RestBaseController<User> {
。。。
}
定义 DynamicPreAuthorize 注解的AOP
@Slf4j
@Aspect
@Component
public class DynamicPreAuthorizeAspect {
@Around("@annotation(dynamicPreAuthorize)")
public Object checkSecurity(ProceedingJoinPoint joinPoint, DynamicPreAuthorize dynamicPreAuthorize) throws Throwable {
ApiInterfaceType interfaceType = dynamicPreAuthorize.interfaceType();
// 获取控制器类上的注解
Class<?> targetClass = joinPoint.getTarget().getClass();
ControllerPermissions controllerPermissions = targetClass.getAnnotation(ControllerPermissions.class);
if (Objects.isNull(controllerPermissions)) {
log.warn("权限配置警告: 控制器 [{}] 尚未定义接口权限映射。请确保已经通过使用 @ControllerPermissions 注解对所需的接口类型指定了相应的权限字符串。", targetClass.getName());
}
if (Objects.nonNull(controllerPermissions)) {
final List<PermissionMapping> mappings = Arrays.stream(controllerPermissions.value())
.filter(mapping -> mapping.interfaceType() == interfaceType)
.toList();
// 获取当前用户的身份验证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
mappings.forEach(mapping -> {
final String[] permissions = mapping.permissions();
for (String requiredPermission : permissions) {
// 根据权限字符串检查权限
if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(requiredPermission))) {
// 用户没有所需的权限,抛出访问拒绝的异常
throw new AccessDeniedException("Access is denied" + requiredPermission);
}
}
});
}
return joinPoint.proceed();
}
}
还可以 检查是否定义权限映射注解
@Slf4j
@Component
public class ControllerPermissionChecker implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, @Nullable String beanName) throws BeansException {
// 检查控制器是否设置了@ControllerPermissions
ControllerPermissions controllerPermissions =
AnnotationUtils.findAnnotation(bean.getClass(), ControllerPermissions.class);
boolean isPermissionMappingDefined = false;
if (Objects.nonNull(controllerPermissions)) {
PermissionMapping[] permissionMappings = controllerPermissions.value();
if (permissionMappings.length == 0) {
System.err.printf("权限配置警告: 控制器 [%s] 使用了@ControllerPermissions注解,但未指定任何权限映射 (PermissionMapping)。请使用 PermissionMapping 指定所需的接口类型和权限字符串。\n", bean.getClass().getName());
} else {
isPermissionMappingDefined = true;
for (PermissionMapping mapping : permissionMappings) {
if (mapping.permissions().length == 0) {
System.err.printf("权限配置警告: 控制器 [%s] 中的接口类型 [%s] 指定了PermissionMapping,但permissions属性为空。请在 PermissionMapping 中指定相应的权限字符串。\n", bean.getClass().getName(), mapping.interfaceType());
}
}
}
}
// 检查具有@DynamicPreAuthorize注解的方法是否在控制器上设置了@ControllerPermissions
Method[] methods = bean.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(DynamicPreAuthorize.class) && !isPermissionMappingDefined) {
System.err.printf("权限配置警告: 控制器 [%s] 中的方法 [%s] 使用了@DynamicPreAuthorize注解,但尚未在控制器级别定义接口权限映射。请确保已经通过使用@ControllerPermissions注解对所需的接口类型指定了相应的权限字符串。\n", bean.getClass().getName(), method.getName());
}
}
return bean;
}
}