BaseController Spring Security @PreAuthorize

前言

公司想把基本的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;
    }
}

你可能感兴趣的:(spring,python,java)