主键id 请求参数用什么类型_Spring Mvc全局id自动加密处理

性能与安全的权衡

对数据库Id字段的设计,各种五花八门的都有。

  • Long类型Id

数据库查询,数值比字符串性能高,正常来说会把主键设计为自增的Long类型,其他表关联也通过该字段来关联,但字段暴露到前端去,用户可以通过简单的修改id获取刷取整站的记录,安全性上存在一定的问题。

  • 字符类型Id

使用字符串如uuid之类的做主键,字段安全属性,但查询效率低下。

如何做到安全与高效兼得呢。

思路

在接口层面将所有id在输出到前端的时候自动做加密,前端提交过来的时候,再自动解密处理成数值类型。

这样是不是就能满足要求了。

  • 对前端来说是一个String,顺便解决了大整数javascript无法处理的问题。

  • 对服务端业务层来说,因框架层面做了解密处理,看到的永远是Long类型的。

需要处理的请求信息包括,query、body、header及respnose里面的id字段

方案

对于互联网来说,任何问题都可以加入一个虚拟的层来解决。

本问题的层,可以在api层,可以在存储层。

  • 存储层

所有Id字段在数据库中是Long类型,但在代码模型上定义为String类型,对DAO做一层封装,统一对Id进行转换,如where id =“xxxxx” 会变成 where id = 111 ,where id in (“xxx”,“yyy”) 变为 where id in(111,222),查询出来的id,在装载模型的时候,自动进行加密处理。

但这存在一个问题,代码中的所有id数据都是加密的,管理后台根C端都会被加密

  • API层

Spring MVC的扩展性足够好,我们可以通过对C端的API进行扩展来满足需求。

  • 雪花算法

类型还是Long,值空间也挺大,只要原样通过ToStringSerializer解析成字符串给前端,就能解决javascript大整数的问题。query参数转化问题,也天然支持。

次方法,高性能、易使用,在一定程度上也不容易刷参数,是一个折中的好选择

实现细节

本文只对API层的处理,做介绍。

为了实现简单对系统做了如下约束:

  • 字段或变量名为id或Id结尾的,Long类型的id字段的充要条件。

  • id不会在header中出现,即我们可以不用处理header

定义个序列化器

    • 重写serialize方法,从gen参数中,可以获取到对应字段的名称

    • 对接口的输出参数识别id字段,并对其进行加密处理,否则原样输出

    • 加密逻辑可以任意替换

  • @JacksonStdImplpublic class LongStringSerializer extends ToStringSerializerBase {    public static final LongStringSerializer instance = new LongStringSerializer();    public LongStringSerializer() {        super(Object.class);    }    public LongStringSerializer(Class> handledType) {        super(handledType);    }    @Override    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {        if(gen.getOutputContext().getCurrentName()!=null                && (gen.getOutputContext().getCurrentName().equals("id") ||                gen.getOutputContext().getCurrentName().endsWith("Id"))){            super.serialize(value, gen, provider);        }        else{            gen.writeNumber((Long)value);        }    }    @SneakyThrows    public final String valueToString(Object value) {        if(value == null ){            return null;        }        return CryptUtils.encrypt(value + "", "idEn");    }}

定义一个反序列化器

    • 覆盖deserialize方法,从jsonParser参数中可获取字段的名称

    • 判断id字段,并对其进行解密操作

    • 解密方法需跟加密方法配套

@JacksonStdImplpublic class LongStringDeSerializer extends JsonDeserializer<Long> {    public static final LongStringDeSerializer instance = new LongStringDeSerializer();    @SneakyThrows    @Override    public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {        if (jsonParser.getCurrentName() == null                || jsonParser.getCurrentName().equals("id")                || jsonParser.getCurrentName().endsWith("Id")) {            return (Long.parseLong(CryptUtils.decrypt(UriEncoder.decode(jsonParser.getText()), "idEn")));        } else {            return Long.parseLong(jsonParser.getText());        }    }}

定义一个Long类型解析器

    • 这个解析器主要用来解析请求参数

    • 处理Long类型即Search对象类型

    • 调用上面的 LongStringDeSerializer对字段进行解密处理

    • 这里暂时只处理了满足项目需求的场景,更多场景需自行细化

public class LongArgumentResolver implements HandlerMethodArgumentResolver {    @Override    public boolean supportsParameter(MethodParameter parameter) {        return parameter.getParameterType().equals(Long.class) || SearchBase.class.isAssignableFrom(parameter.getParameterType());    }    @Override    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {        ObjectMapper objectMapper = new ObjectMapper();        SimpleModule simpleModule = new SimpleModule();        simpleModule.addDeserializer(Long.class, LongStringDeSerializer.instance);        simpleModule.addDeserializer(Long.TYPE, LongStringDeSerializer.instance);        objectMapper.registerModule(simpleModule);        if (parameter.getParameterType().equals(Long.class)) {            var query = ((ServletWebRequest) webRequest).getRequest().getQueryString();            var map = getQueryParams(query);            var data = UriEncoder.decode(map.entrySet().stream().findFirst().map(Map.Entry::getValue).orElse(null));            if (Longs.tryParse(data) != null) {                return data;            }            return objectMapper.readValue(JSON.toJSONString(data)                    , parameter.getParameterType());        }        if (SearchBase.class.isAssignableFrom(parameter.getParameterType())) {            if (((ServletWebRequest) webRequest).getHttpMethod().equals(HttpMethod.GET)) {                var query = ((ServletWebRequest) webRequest).getRequest().getQueryString();                var map = getQueryParams(query);                return objectMapper.readValue(                        JSON.toJSONString(map), parameter.getParameterType());            }        }        return null;    }    /**     * Retrieve the query parameters from given url     *     * @return params     Map with query parameters     * @throws IOException     */    static public Map getQueryParams(String query)            throws IOException {        Map params = new HashMap();        if (query == null) {            return params;        }        Arrays.stream(query.split("&")).forEach(e -> {            var token = e.split("=");            if (token.length == 2) {                params.put(token[0], token[1]);            }        });        return params;    }}

修改Mvc配置

    • 添加HttpMessageConverter,将LongStringSerializer及LongStringDeSerializer配置为Long类型的序列化处理器

    • 添加HandlerMethodArgumentResolver,将LongArgumentResolver配置为请求参数的处理器

@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {    @Override    public void configureMessageConverters(List> converters) {        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();        ObjectMapper objectMapper = objectMapper();        jackson2HttpMessageConverter.setObjectMapper(objectMapper);        converters.add(jackson2HttpMessageConverter);    }    @Bean    ObjectMapper objectMapper() {        ObjectMapper objectMapper = new ObjectMapper();        SimpleModule simpleModule = new SimpleModule();        simpleModule.addSerializer(Long.class, LongStringSerializer.instance);        simpleModule.addSerializer(Long.TYPE, LongStringSerializer.instance);        simpleModule.addDeserializer(Long.class, LongStringDeSerializer.instance);        simpleModule.addDeserializer(Long.TYPE, LongStringDeSerializer.instance);        objectMapper.registerModule(simpleModule);        return objectMapper;    }    @Bean    public WebMvcConfigurer webMvcConfigurer() {        return new WebMvcConfigurerAdapter() {            @Override            public void addArgumentResolvers(List argumentResolvers) {                argumentResolvers.add(new LongArgumentResolver());            }        };    }}

类关系图

9d8d61e496c74fa58f4ebef779070b4b.png

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值