概述
性能与安全的权衡
对数据库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()); } }; }}
类关系图
最后
以上就是清爽石头为你收集整理的主键id 请求参数用什么类型_Spring Mvc全局id自动加密处理性能与安全的权衡思路方案实现细节的全部内容,希望文章能够帮你解决主键id 请求参数用什么类型_Spring Mvc全局id自动加密处理性能与安全的权衡思路方案实现细节所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复