SpringMvc/SpringBoot HTTP通信加解密
来源:互联网 发布:电力猫网络dns不正常 编辑:程序博客网 时间:2024/05/29 09:16
前言
从去年10月份到现在忙的没时间写博客了,今天就甩给大家一个干货吧!!!
近来很多人问到下面的问题
- 我们不想在每个Controller方法收到字符串报文后再调用一次解密,虽然可以完成,但是很low,且如果想不再使用加解密,修改起来很是麻烦。
- 我们想在使用Rest工具或swagger请求的时候不进行加解密,而在app调用的时候处理加解密,这可如何操作。
针对以上的问题,下面直接给出解决方案:
实现思路
- APP调用API的时候,如果需要加解密的接口,需要在httpHeader中给出加密方式,如
header[encodeMethod]
。 - Rest工具或swagger请求的时候无需指定此header。
- 后端API收到request后,判断header中的
encodeMethod
字段,如果有值,则认为是需要解密,否则就认为是明文。
约定
为了精简分享技术,先约定只处理POST上传JSON(application/json)数据的加解密处理
。
请求解密实现方式
1. 先定义controller
@Controller@RequestMapping("/api/demo")public class MyDemoController { @RequestDecode @ResponseBody @RequestMapping(value = "user", method = RequestMethod.POST) public ResponseDto addUser( @RequestBody User user ) throws Exception { //TODO ... }}
/** * 解密请求数据 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestDecode { SecurityMethod method() default SecurityMethod.NULL;}
可以看到这里的Controller定义的很普通,只有一个额外的自定义注解RequestDecode
,这个注解是为了下面的RequestBodyAdvice的使用。
2. 建设自己的RequestBodyAdvice
有了上面的入口定义,接下来处理解密
这件事,目的很明确:
1. 是否需要解密判断httpHeader中的encodeMethod
字段。
2. 在进入controller之前就解密完成,是controller处理逻辑无感知。
DecodeRequestBodyAdvice.java
@Slf4j@Component@ControllerAdvice(basePackages = "com.xxx.hr.api.controller")public class DecodeRequestBodyAdvice implements RequestBodyAdvice { @Value("${hrapi.aesKey}") String aesKey; @Value("${hrapi.googleKey}") String googleKey; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.getMethodAnnotation(RequestDecode.class) != null && methodParameter.getParameterAnnotation(RequestBody.class) != null; } @Override public Object handleEmptyBody(Object body, HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { RequestDecode requestDecode = parameter.getMethodAnnotation(RequestDecode.class); if (requestDecode == null) { return request;//controller方法不要求加解密 } String appId = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.APP_ID);//这里是扩展,可以知道来源方(如开放平台使用) String encodeMethod = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.ENCODE_METHOD); if (StringUtils.isEmpty(encodeMethod)) { return request; } SecurityMethod encodeMethodEnum = SecurityMethod.getByCode(encodeMethod); //这里灵活的可以支持到多种加解密方式 switch (encodeMethodEnum) { case NULL: break; case AES: { InputStream is = request.getBody(); ByteBuf buf = PooledByteBufAllocator.DEFAULT.heapBuffer(); int ret = -1; int len = 0; while((ret = is.read()) > 0) { buf.writeByte(ret); len ++; } String body = buf.toString(0, len, xxxSecurity.DEFAULT_CHARSET); buf.release(); String temp = null; try { temp = XxxSecurity.aesDecodeData(body, aesKey, googleKey, new CheckCallBack() { @Override public boolean isRight(String data) { return data != null && (data.startsWith("{") || data.startsWith("[")); } }); log.info("解密完成: {}", temp); return new DecodedHttpInputMessage(request.getHeaders(), new ByteArrayInputStream(temp.getBytes("UTF-8"))); } catch (DecodeException e) { log.warn("解密失败 appId: {}, Name:{} 待解密密文: {}", appId, partnerName, body, e); throw e; } } } return request; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } static class DecodedHttpInputMessage implements HttpInputMessage { HttpHeaders headers; InputStream body; public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) { this.headers = headers; this.body = body; } @Override public InputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } }}
至此加解密完成了。
————————-华丽分割线 —————————–
响应
加密
下面附件一下响应
加密过程,目的
1. Controller逻辑代码无感知
2. 可以一键开关响应加密
定义Controller
@ResponseEncode @ResponseBody @RequestMapping(value = "employee", method = RequestMethod.GET) public ResponseDto<UserEEInfo> userEEInfo( @ApiParam("用户编号") @RequestParam(HttpHeaders.APPID) Long userId ) { //TODO ... }
/** * 加密响应数据 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ResponseEncode { SecurityMethod method() default SecurityMethod.NULL;}
这里的Controller定义的也很普通,只有一个额外的自定义注解ResponseEncode
,这个注解是为了下面的ResponseBodyAdvice的使用。
建设自己的ResponseBodyAdvice
这里约定将响应的DTO序列化为JSON格式数据,然后再加密
,最后在响应给请求方。
@Slf4j@Component@ControllerAdvice(basePackages = "com.xxx.hr.api.controller")public class EncodeResponseBodyAdvice implements ResponseBodyAdvice { @Autowired PartnerService partnerService; @Override public boolean supports(MethodParameter returnType, Class converterType) { return returnType.getMethodAnnotation(ResponseEncode.class) != null; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ResponseEncode responseEncode = returnType.getMethodAnnotation(ResponseEncode.class); String uid = request.getHeaders().getFirst(HttpHeaders.PARTNER_UID); if (uid == null) { uid = request.getHeaders().getFirst(HttpHeaders.APP_ID); } PartnerConfig config = partnerService.getConfigByAppId(uid); if (responseEncode.method() == SecurityMethod.NULL || responseEncode.method() == SecurityMethod.AES) { if (config == null) { return ResponseDto.rsFail(ResponseCode.E_403, "商户不存在"); } String temp = JSON.toJSONString(body); log.debug("待加密数据: {}", temp); String encodedBody = MLJRSecurity.aesEncodeData(temp, config.getEncryptionKey(), config.getGoogleKey()); log.debug("加密完成: {}", encodedBody); response.getHeaders().set(HttpHeaders.ENCODE_METHOD, HttpHeaders.VALUE.AES); response.getHeaders().set(HttpHeaders.HEADER_CONTENT_TYPE, HttpHeaders.VALUE.APPLICATION_BASE64_JSON_UTF8); response.getHeaders().remove(HttpHeaders.SIGN_METHOD); return encodedBody; } return body; }}
拓展
由上面的实现,如何实现RSA验证签名呢?这个就简单了,请看分解。
目的还是很简单,进来减少对业务逻辑的入侵。
首先设定一下那些请求需要验证签名
@RequestSign @ResponseEncode @ResponseBody @RequestMapping(value = "employee", method = RequestMethod.GET) public ResponseDto<UserEEInfo> userEEInfo( @ApiParam("用户编号") @RequestParam(HttpHeaders.UID) String uid ) { //TODO ... }
这里还是使用一个注解RequestSign
,然后再实现一个SignInterceptor即可完成:
@Slf4j@Componentpublic class SignInterceptor implements HandlerInterceptor { @Autowired PartnerService partnerService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod method = (HandlerMethod) handler; RequestSign requestSign = method.getMethodAnnotation(RequestSign.class); if (requestSign == null) { return true; } String appId = request.getHeader(HttpHeaders.APP_ID); ValidateUtils.notTrimEmptyParam(appId, "Header[appId]"); PartnerConfig config = partnerService.getConfigByAppId(appId); ValidateUtils.notNull(config, Code.E_400, "商戶不存在"); String partnerName = partnerService.getPartnerName(appId); String sign = request.getParameter(HttpHeaders.SIGN); String signMethod = request.getParameter(HttpHeaders.SIGN_METHOD); signMethod = (signMethod == null) ? "RSA" : signMethod; Map<String, String[]> parameters = request.getParameterMap(); ValidateUtils.notTrimEmptyParam(sign, "sign"); if ("RSA".equals(signMethod)) { sign = sign.replaceAll(" ", "+"); boolean isOK = MLJRSecurity.signVerifyRequest(parameters, config.getRsaPublicKey(), sign, config.getSecurity()); if (isOK) { log.info("验证商户签名通过 {}[{}] ", appId, partnerName); return true; } else { log.warn("验证商户签名失败 {}[{}] ", appId, partnerName); } } else { throw new SignVerifyException("暂不支持该签名"); } throw new SignVerifyException("签名校验失败"); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}
阅读全文
2 0
- SpringMvc/SpringBoot HTTP通信加解密
- springboot 配置文件明文加解密
- 通信加解密
- 了解网络通信中的加解密
- 客户端和服务端通信加解密
- url加解密之http代码
- JavaScript实现http请求数据加解密
- EasyUI+SpringMVC登录路径要加http://
- Android 和服务器通信共有的des加解密
- UKEY通信各个应用的流程介绍--数据加解密
- 加解密
- 加解密
- 加解密
- 加解密
- 加解密
- 加解密
- 加解密
- 【SpringBoot】AutoConfiguration 解密
- 算法学习记录七(C++)--->二分法找有序旋转数组最小值
- Floyd+Bellman-ford求正环 hdu1317 XYZZY
- Lucene(三)索引域选项
- 二叉树
- MongoDB 数据同步至 ElasticSearch (Mongo-connector)
- SpringMvc/SpringBoot HTTP通信加解密
- iOS中自定义button、button属性、swift
- java List 指定属性排序
- 逆向实战之入门去除某果TV广告
- 蓝牙BlueTooth
- Maven install报MojoExecutionException
- linux安装bcompare
- 关于调用listFile()在遍历含有乱码文件时出现input is not valid Modified UTF-8错误的解决方法
- ES6之------------------let,const