如何安全的完成DTO转Domain, 主要是安全哦, 我有3个方案还有别的吗

例如

在做更新用户信息的时候, controller 接收 UserDTO, 然后调用 BeanCopy 相关方法, 将 UserDTO 转换成为真正的 User 对象, 方式就是若属性名字相同就copy, 然后再server中执行更新用户操作

那么如果不怀好意的用户在前台传递了一个 password 参数, 那么当 dto -> domain 的时候, User 的 password 就被赋值了, 之后 Mybatis 的动态SQL就错误的拼接上了 set password = :password , 然后就用户密码就被篡改了

目前我有几种方案, 不知还有没有更加优雅的

方案1: 为该功能单独设计一个 DTO 对象, 其中只保留接口相关的属性, 这样dto中就去掉了password属性, 好处就是从源头上把控了, 缺点就是有可能DTO会非常多

方案2: mybatis的sql语句单独写一个用户更新的sql, 把拼接passowrd那一行删掉, 好处就是dto不用改, 用一个统一的dto就全搞定了, 缺点就是要单独写一个sql语句, 不能使用统一的动态sql了, 有可能sql会越来越多

方案3: 在beancopy部分下功夫, 单独写一个更新用户的beancopy, 只copy该功能相关的属性, 缺点就是有可能会写很多定制的copy方法

大家有什么建议?? 我倾向于 方案1, 建立多个DTO

阅读 6.8k
2 个回答

第一种方式最好,一个类应该只包含其所需要的属性。

DTO多其实无所谓,在你的场景里DTO对应的是UI。从需求层面上来讲,UI变动是比较频繁的,DTO作为一个轻量的类,为不同UI设计不同的DTO是很正常的,而且这样一来,代码修改所带来的影响面就只停留在表现层,而不是领域层。

你的第二个方法,属于将UI的需求影响到了数据存储逻辑的底层,这个是不对的。

你的第三个方法,属于将UI的需求影响到了底层第三方框架,如果哪天你换了框架就麻烦了,而且底层代码和表现层脱离,不易于理解。

之前考虑过。

一个转换的接口

/**
 * @author cyh
 * @Description: 对象转换接口
 * @version 1.0.0
 */
public interface Converter<T, V> {
    V convert(T t);
}

一个生产 get set 方法的工具类

/**
 * @author cyh
 * @Description: 用于生成 get set 方法
 */
class ManualConvertCodeGenerater {


    private static Set<String> getAllFieldNames(Class clz) {
        Set<String> result = new HashSet<>();
        Field[] fields = clz.getDeclaredFields();//获得属性
        for(Field one:fields) {
            result.add(one.getName());
        }
        if(clz.getGenericSuperclass() != null) {
            Class supClz = clz.getSuperclass();
            result.addAll(getAllFieldNames(supClz));
        }
        return result;
    }

    public static void generate(String fromName, Class fromClass, String toName, Class toClass) {
        Set<String> sets = getAllFieldNames(toClass);
        Set<String> gets = getAllFieldNames(fromClass);
        Set<String> setNoFind = new HashSet<>();
        for (String setName : sets) {
            if(setName.equals("serialVersionUID")) continue;
            boolean hasGet = false;
            for(String getName : gets) {
                if(setName.equals(getName)) {
                    hasGet = true;
                    String result = toName + "." + "set";
                    result += Character.toUpperCase(setName.charAt(0)) + setName.substring(1);
                    result += "(";
                    result += fromName + "." + "get";
                    result += Character.toUpperCase(getName.charAt(0)) + getName.substring(1);
                    result += "());";
                    System.out.println(result);
                }
            }
            if(!hasGet) {
                setNoFind.add(setName);
            }
        }

        for(String one:setNoFind) {
            String result = toName + "." + "set";
            result += Character.toUpperCase(one.charAt(0)) + one.substring(1);
            result += "();";
            System.out.println(result);
        }
    }

}

一个实际的转换器,隐藏于包中。

/**
 * @author cyh
 * @Description: 图片vo 转 实体
 * @version 1.0.0
 */
class ImagePublishVo2XfImage implements Converter<ImagePublishVo, XfImage> {

    static ImagePublishVo2XfImage instance = new ImagePublishVo2XfImage();

    private ImagePublishVo2XfImage() {
    }

    /**
     * 手动复制
     */
    private XfImage manualConvert(ImagePublishVo imagePublishVo) {
        if(imagePublishVo == null) return null;
        XfImage image = new XfImage();
        image.setCover(imagePublishVo.getCover());
        image.setName(imagePublishVo.getName());
        image.setPath(imagePublishVo.getPath());
        image.setType(imagePublishVo.getType());
        image.setSyPath(imagePublishVo.getSyPath());
        image.setUniqueKey(imagePublishVo.getUniqueKey());
        image.setWatermark(imagePublishVo.getWatermark());
        image.setOriImg(imagePublishVo.getPath());
        return image;
    }

    /**
     * 使用工具复制
     */
    @Deprecated
    private XfImage utilConvert(ImagePublishVo imagePublishVo) {
        if(imagePublishVo == null) return null;
        XfImage image = new XfImage();
        BeanUtils.copyProperties(imagePublishVo, image);
        return image;
    }

    @Override
    public XfImage convert(ImagePublishVo imagePublishVo) {
        return manualConvert(imagePublishVo);
    }

    public static void main(String[] args) {
        ManualConvertCodeGenerater.generate("imagePublishVo", ImagePublishVo.class, "image", XfImage.class);
    }
}

管理所有转换器,对外提供服务

/**
 * @author cyh
 * @Description: 转换器上下文,封装具体转换过程
 * @version 1.0.0
 */
public class ConverterContext {
    /** 保存所有可用的转换器 **/
    private static Map<Class, Map<Class, Converter>> converters = new HashMap<>();
    /** 添加可用的转换器 **/
    private static <T, V> void put(Class<T> from, Class<V> to, Converter<T, V> converter) {
        Map<Class, Converter> map = converters.get(from);
        if(map == null) map = new HashMap<>();
        map.put(to, converter);
        converters.put(from, map);
    }
    /** 初始化 **/
    static {
        put(ImagePublishVo.class, XfImage.class, ImagePublishVo2XfImage.instance);
    }


    /**
     * 默认转换
     *
     * @param to
     *      转换后的对象类型
     * @param o
     *      转换前的对象
     * @return
     *      转换后的对象,可能为 null
     */
    private static  <V> V defaultConvert(Class<V> to, Object o) {
        V result = null;
        try {
            result = to.newInstance();
            BeanUtils.copyProperties(o, result);
        } catch (InstantiationException e) {
            // TODO: 2016/9/7
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO: 2016/9/7
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 具体的转换动作
     *
     * @param to
     *      转换后的对象类型
     * @param o
     *      被转换的对象
     * @return
     *      转换后的对象,可能为 null
     */
    public static <T, V> V doConvert(T o, Class<V> to) {
        if(o == null || to == null) return null;
        V result = null;
        Converter<T, V> converter;
        Map<Class, Converter> map = converters.get(o.getClass());
        if(map != null) {
            converter = map.get(to);
            if(converter != null) {
                result = converter.convert(o);
            } else {
                result = defaultConvert(to, o);
            }
        } else {
            result = defaultConvert(to, o);
        }
        return result;
    }

}

BeanUtils 是 spring 提供的一个工具类,差异不大不需要过滤的直接走这个默认的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题