前后端分离项目接口权限检查方案

基于handleMethod写的一款分级式接口权限检查方案。
权限自动同步机制(启动更新,页面不提供增删改):

public class AuthorizationMappingGenerateExecutor implements EasyApplicationRunner {

    @Autowired
    private AuthorizationMappingMapper authorizationMappingMapper;
    @Autowired
    private RequestMappingHandlerMapping mapping;
    @Autowired
    private SupportSecurityProperties securityProperties;
	// 启动时执行一次,随便用用作个排序。没安全问题
    int i = 0;

    @Override
    public void doBusiness() {
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = mapping.getHandlerMethods();
        Collection<HandlerMethod> handlerMethods = handlerMethodMap.values();
        // 表中已存在的code
        List<String> existsCodes = authorizationMappingMapper.selectProperties(Wrappers.lambdaQuery(), AuthorizationMapping::getCode);
        // 排重set
        Set<String> currentExistsCodes = new HashSet<>();
        // 配置的级别
        MappingLevel mappingLevel = securityProperties.getMappingLevel();
	
		// 添加的权限
        List<AuthorizationMapping> insertMappings = new ArrayList<>();
        // 需要更新的权限
        List<AuthorizationMapping> updateMappings = new ArrayList<>();

        for (HandlerMethod handlerMethod : handlerMethods) {
            Class<?> beanType = handlerMethod.getBeanType();
            String name = beanType.getName();
            // 只取本系统内的接口
            if (!name.contains("xxxx")) {
                continue;
            }

            AuthorizationMapping moduleMapping = buildModuleMapping(handlerMethod);
            int type = chooseType(existsCodes, currentExistsCodes, moduleMapping);
            if (type == 1) {
                insertMappings.add(moduleMapping);
            } else if (type == 2) {
                updateMappings.add(moduleMapping);
            }
            // 只开启了一级权限检查
            if (mappingLevel.is(MappingLevel.MODULE)) {
                continue;
            }

            AuthorizationMapping controllerMapping = buildControllerMapping(moduleMapping.getCode(), handlerMethod);
            int controllerType = chooseType(existsCodes, currentExistsCodes, controllerMapping);
            if (controllerType == 1) {
                insertMappings.add(controllerMapping);
            } else if (controllerType == 2) {
                updateMappings.add(controllerMapping);
            }
            // 只开启了二级权限检查
            if (mappingLevel.is(MappingLevel.CONTROLLER)) {
                continue;
            }

            // 方法级的权限检查
            AuthorizationMapping methodMapping = buildMethodMapping(controllerMapping.getCode(), handlerMethod);
            int methodType = chooseType(existsCodes, currentExistsCodes, methodMapping);
            if (methodType == 1) {
                insertMappings.add(methodMapping);
            } else if (methodType == 2) {
                updateMappings.add(methodMapping);
            }
        }
		// 批量插入
        authorizationMappingMapper.insertSplitBatch(insertMappings, 200);

		// 配置是否更新,一条条更新太慢,影响启动速度。
        if (securityProperties.getEnableMappingUpdate()) {
            updateMappings.forEach(authorizationMappingMapper::updateById);
        }
        // 清掉子级,不占表空间。开放的话重启构建即可。code不会变
        LambdaQueryWrapper<AuthorizationMapping> wrapper = Wrappers.lambdaQuery();
        if (mappingLevel.is(MappingLevel.MODULE)) {
            wrapper.in(AuthorizationMapping::getLevel, ZYListUtils.toList(MappingLevel.CONTROLLER.level(), MappingLevel.METHOD.level()));
        } else if (mappingLevel.is(MappingLevel.CONTROLLER)) {
            wrapper.eq(AuthorizationMapping::getLevel, MappingLevel.METHOD.level());
        }
        authorizationMappingMapper.delete(wrapper);
    }

    private int chooseType(List<String> existsCodes, Set<String> currentExistsCodes, AuthorizationMapping methodMapping) {
        String code = methodMapping.getCode();
        if (currentExistsCodes.contains(code)) {
            return 0;
        }
        // 去重
        currentExistsCodes.add(methodMapping.getCode());
        // 更新or插入
        if (!existsCodes.contains(code)) {
            return 1;
        } else {
            return 2;
        }
    }

	// 构建模块权限
    private AuthorizationMapping buildModuleMapping(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        Package aPackage = beanType.getPackage();

        AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.MODULE);
        packageMapping.setMapping("");
        packageMapping.setParentCode(ZYTreeUtils.TREE_ROOT_ID);

        String moduleCode = PermissionMappingGenerator.generateModuleKey(handlerMethod);
        packageMapping.setCode(moduleCode);

        String packageName = aPackage.getName();
        packageMapping.setFileLocal(packageName);
        String packageChinaName = try2getPackageChinaName(handlerMethod);
        packageMapping.setName(null != packageChinaName ? packageChinaName : packageName);

        return packageMapping;
    }

	// 构建controller级权限
    private AuthorizationMapping buildControllerMapping(String parentCode, HandlerMethod handlerMethod) {

        AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.CONTROLLER);
        packageMapping.setMapping("");
        packageMapping.setParentCode(parentCode);

        String controllerCode = PermissionMappingGenerator.generateControllerKey(handlerMethod);
        packageMapping.setCode(controllerCode);

        Class<?> beanType = handlerMethod.getBeanType();
        packageMapping.setFileLocal(beanType.getName());
        Theme theme = beanType.getAnnotation(Theme.class);
        packageMapping.setName(null != theme ? theme.value() : beanType.getSimpleName());

        return packageMapping;
    }

	// 构建方法级权限
    private AuthorizationMapping buildMethodMapping(String parentCode, HandlerMethod handlerMethod) {

        String methodCode = PermissionMappingGenerator.generateMethodKey(handlerMethod);
        AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.METHOD);
        packageMapping.setCode(methodCode);
        packageMapping.setParentCode(parentCode);

        Class<?> beanType = handlerMethod.getBeanType();
        Method method = handlerMethod.getMethod();
        packageMapping.setFileLocal(beanType.getName() + "." + method.getName());
        packageMapping.setMapping(spellMapping(method, beanType));
        packageMapping.setName(getMappingName(handlerMethod));

        return packageMapping;
    }

    private AuthorizationMapping buildBaseMapping(MappingLevel level) {
        AuthorizationMapping packageMapping = new AuthorizationMapping();
        packageMapping.setCreateDate(System.currentTimeMillis());
        packageMapping.setIsMiniAuth(0);
        packageMapping.setSort(++i);
        packageMapping.setLevel(level.level());
        packageMapping.setRemarks(level.getMappingName());
        return packageMapping;
    }

    private String getMappingName(HandlerMethod handlerMethod) {
        SystemLog systemLog = handlerMethod.getMethodAnnotation(SystemLog.class);
        if (null != systemLog) {
            return systemLog.value();
        }
        return handlerMethod.getMethod().getName();
    }

    private String spellMapping(Method method, Class<?> beanType) {
        StringBuilder path = new StringBuilder();
        RequestMapping parentRequestMapping = beanType.getAnnotation(RequestMapping.class);
        if (null != parentRequestMapping) {
            String[] value = parentRequestMapping.value();
            if (value.length > 0) {
                path.append(value[0]);
            }
        }
        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
        GetMapping methodGetRequestMapping = method.getAnnotation(GetMapping.class);
        PostMapping methodPostRequestMapping = method.getAnnotation(PostMapping.class);
        if (null != methodRequestMapping) {
            String[] value = methodRequestMapping.value();
            if (value.length > 0) {
                path.append(value[0]);
            }
        } else if (null != methodGetRequestMapping) {
            String[] value = methodGetRequestMapping.value();
            if (value.length > 0) {
                path.append(value[0]);
            }
        } else if (null != methodPostRequestMapping) {
            String[] value = methodPostRequestMapping.value();
            if (value.length > 0) {
                path.append(value[0]);
            }
        }
        return path.toString();
    }

    private String try2getPackageChinaName(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        Package aPackage = beanType.getPackage();
        Theme packageTheme = aPackage.getAnnotation(Theme.class);
        if (null != packageTheme) {
            return packageTheme.value();
        }
        return null;
    }
}

权限检查拦截器

public class AuthorizationMappingInterceptor implements EasyMvcInterceptor, InitializingBean {

    private final static Set<String> NO_NEED_CHECK_CODES = new HashSet<>();

    private final static Set<String> CHECK_CODES = new HashSet<>();

    @Autowired
    private AuthorizationMappingMapper authorizationMappingMapper;
    @Autowired
    private SupportSecurityProperties securityProperties;


    @Override
    public boolean doBusiness(HandlerMethod handlerMethod, HttpServletRequest request, HttpServletResponse response) {
    	// 开放接口
        if (ZYRequestUtils.isDirectApi()) {
            return true;
        }
		// 超管
        if (UserHelper.isSuperAdmin()) {
            return true;
        }

		// 需要跳过的权限
        if (isSkipMappingCheck(handlerMethod)) {
            return true;
        }

        // 查询需要的权限
        MappingLevel mappingLevel = securityProperties.getMappingLevel();
        String permissionCode = PermissionMappingGenerator.generateByLevel(handlerMethod, mappingLevel);
        // 缓存在存在,直接通过检查
        if (NO_NEED_CHECK_CODES.contains(permissionCode)) {
            return true;
        }

        // 该接口没有做权限限制,直接放行
        if (!CHECK_CODES.contains(permissionCode)) {
            NO_NEED_CHECK_CODES.add(permissionCode);
            return true;
        }

        // 用户权限信息
        LoginUser loginAreaUser = UserHelper.getLoginAreaUser();
        Set<String> mappingCodes = loginAreaUser.getMappingCodes();
        if (!hasPermission(permissionCode, mappingCodes)) {
            throw new PermissionException("您没有访问接口的权限");
        }

        return true;
    }

    private boolean hasPermission(String permissionCode, Set<String> mappingCodes) {
        return null != mappingCodes && mappingCodes.contains(permissionCode);
    }

    @Override
    public void afterPropertiesSet() {
        // 根据权限级别查询需要检查的HandleMethod或controller
        MappingLevel mappingLevel = securityProperties.getMappingLevel();
        LambdaQueryWrapper<AuthorizationMapping> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(AuthorizationMapping::getLevel, mappingLevel.level());
        wrapper.eq(AuthorizationMapping::getIsMiniAuth, NO);
        wrapper.select(AuthorizationMapping::getCode);
        List<String> authorizationMappings = authorizationMappingMapper.selectProperties(wrapper, AuthorizationMapping::getCode);
        CHECK_CODES.addAll(authorizationMappings);
    }

    private boolean isSkipMappingCheck(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        Package aPackage = beanType.getPackage();
        SkipMappingCheck packageSkip = aPackage.getAnnotation(SkipMappingCheck.class);
        if (null != packageSkip) {
            return true;
        }

        SkipMappingCheck classSkip = beanType.getAnnotation(SkipMappingCheck.class);
        if (null != classSkip) {
            return true;
        }
        Method method = handlerMethod.getMethod();
        SkipMappingCheck methodSkip = method.getAnnotation(SkipMappingCheck.class);
        return null != methodSkip;
    }
}

public enum MappingLevel implements Matcher {

    MODULE("模块"),
    CONTROLLER("控制类"),
    METHOD("接口方法");

    private String mappingName;

    public String level() {
        return this.name().toLowerCase();
    }

    @Override
    public Object statusCode() {
        return level();
    }
}

code生成器

public class PermissionMappingGenerator {

    private final static Map<String, String> PACKAGE_KEY_CACHE = new HashMap<>();
    private final static Map<String, String> CONTROLLER_KEY_CACHE = new HashMap<>();
    private final static Map<String, String> METHOD_KEY_CACHE = new HashMap<>();

    public static String generateByLevel(HandlerMethod handlerMethod, MappingLevel mappingLevel) {
        switch (mappingLevel) {
            case METHOD:
                return generateMethodKey(handlerMethod);
            case CONTROLLER:
                return generateControllerKey(handlerMethod);
            case MODULE:
                return generateModuleKey(handlerMethod);
            default:
                throw new LocalException("不能确定的权限级别");
        }
    }

    public static String generateModuleKey(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        Package aPackage = beanType.getPackage();
        String packageName = aPackage.getName();
        String cryptPackageCode = PACKAGE_KEY_CACHE.get(packageName);
        if (ZYStrUtils.isNull(cryptPackageCode)) {
            cryptPackageCode = ZYCryptUtils.encryptMD5(packageName).toLowerCase();
            PACKAGE_KEY_CACHE.put(packageName, cryptPackageCode);
        }
        return cryptPackageCode;
    }

    public static String generateControllerKey(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        String className = beanType.getName();
        String cryptControllerCode = CONTROLLER_KEY_CACHE.get(className);
        if (ZYStrUtils.isNull(cryptControllerCode)) {
            cryptControllerCode = ZYCryptUtils.encryptMD5(className).toLowerCase();
            CONTROLLER_KEY_CACHE.put(className, cryptControllerCode);
        }
        return cryptControllerCode;
    }

    public static String generateMethodKey(HandlerMethod handlerMethod) {
        Class<?> beanType = handlerMethod.getBeanType();
        Method method = handlerMethod.getMethod();

        List<String> keySign = new ArrayList<>();
        keySign.add(beanType.getSimpleName());
        keySign.add(method.getName());

        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            keySign.add(parameter.getName());
        }
        String key = StrUtils.join(keySign, "#");
        String cryptKey = METHOD_KEY_CACHE.get(key);
        if (null == cryptKey) {
            cryptKey = CryptUtils.encryptMD5(key).toLowerCase();
            METHOD_KEY_CACHE.put(key, cryptKey);
        }
        return cryptKey;

    }
}

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipMappingCheck {
}

与传统的路径匹配接口权限检查相比,本方案采用HandleMethod信息与角角直接绑定。多了一个权限颗粒度划分。对减少接品权限数量及减少人工分配量有明显的优势。自动的权限生成工具省去了维护权限编号协删改查的人工操作。减去了大量人工。实际使用中,默认使用module级,维护量很少。较严情况下,使用controller级。严格安全等保要求的情况下。使用mothod级。方案选择很灵活。重启服务即可任意切换。可以使用SkipMappingCheck 注解对整个模块,整个controller进行跳过处理。

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