美文网首页
springmvc 自定义消息转换器完整例子

springmvc 自定义消息转换器完整例子

作者: 索隆大大 | 来源:发表于2018-12-10 23:55 被阅读100次

    问题描述:
    最近在项目中对接第三方接口,采用http协议,post方法,协议类型:Content-Type: application/json;charset=utf-8,将用户名和密码等信息放在header中,用于验证请求。将业务数据放到body体中,并使用3DES加密。

    • 请求报文样例如下:

    POST /api/GetParkingPaymentInfo HTTP/1.1
    Content-Type: application/json;charset=utf-8
    user: 123453
    pwd: qwerew
    Host: 220.160.112.124:9096
    Content-Length: 43
    Expect: 100-continue
    {"data":"DkTwRsUUza33A8/TvrocXI3r+Az1T7bt"}

    每个接口都是此加密验证方式,但是我不想再每个controller方法中都校验解密一次,故而想到使用springmvc 的自定义消息转换器,在消息转换器中先解密,然后将报文转换为对应的java对象,controller入参直接是java对象,这样校验用户名密码和解密就可以单独处理了。

    验证用户名和密码,使用拦截器实现

    因为用户名和密码放到了header中,可以在拦截器中获取请求头,判断用户名和密码是否正确。

    • 创建拦截器
    @Component
    public class KeyTopInterceptor extends HandlerInterceptorAdapter {
    
        private static final Logger log = LoggerFactory.getLogger(AuthInterceptor.class);
    
        private static final String MIME_JSON = "application/json;charset=UTF-8";
        @Value("${keytop.user}")
        private String ktuser;
        @Value("${keytop.pwd}")
        private String ktpwd;
        //在请求进入controller前进行拦截
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            String user = request.getHeader("user");
            String pwd = request.getHeader("pwd");
            String host = request.getHeader("Host");
            log.info("===校验科托请求头中的用户名和密码,url={},user={},pwd={},host={}",request.getRequestURI(),user,pwd,host);
            if(ktuser.equals(user) && ktpwd.equals(pwd)){
                return true;
            }else{
                log.info("===校验科托失败,配置的用户名和密码与传递的不一致,配置的ktuser={},ktpwd={}",ktuser,ktpwd);
                //根据接口要求返回错误信息
                PrintWriter writer = response.getWriter();
                response.setCharacterEncoding("UTF-8");
                response.setHeader("Content-type", MIME_JSON);
                response.setContentType(MIME_JSON);
                BaseKeyTopRes<?> baseKeyTopRes = new BaseKeyTopRes<>();
                baseKeyTopRes.setFaileInfo("user or pwd incorrectness");
                response.setStatus(HttpStatus.OK.value());
                writer.write(JSONObject.toJSON(baseKeyTopRes).toString());
                writer.close();
                return false;
            }
        }
    }
    
    • 配置拦截器
    @Configuration
    @EnableWebMvc
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    
        @Autowired
        private KeyTopInterceptor keyTopInterceptor;
    
        /**
         * 添加拦截器
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加科托拦截器
            registry.addInterceptor(keyTopInterceptor)
                    .addPathPatterns("/keytop/**");
        }
    
    }
    

    body体解密,转换为java对象

    例如有个接口的data字段为:{“data”:{"platno":"A1234"}},获取到的参数为加密之后的:{"data":"DkTwRsUUza33A8/TvrocXI3r+Az1T7bt"}。

    • 创建消息转换器
      为了使创建的消息转换器只转换本次业务新增的接口,创建一个请求基类bean对象,没有任何字段,只是实现Serializable接口,作为其他业务的父类,例如:BaseKeyTopReq
    public class BaseKeyTopReq implements Serializable{
    
    }
    

    创建消息转换器如下:

    public class KeyTopMsgConverter extends AbstractHttpMessageConverter<BaseKeyTopReq> {
        private static final Logger logger = LoggerFactory.getLogger(KeyTopMsgConverter.class);
        //科托3DES加解密需要的key
        private String ktkey;
        //科托3DES加解密需要的偏移量
        private String ktiv;
    
        public KeyTopMsgConverter(MediaType supportedMediaType,String ktkey,String ktiv) {
            super(supportedMediaType);
            this.ktiv=ktiv;
            this.ktkey=ktkey;
        }
    
        /**
         * 如果支持 true支持
         *  会调用 readInternal 将http消息 转换成方法中被@RequestBody注解的参数
         *  会调用writeInternal 将被@ResponseBody注解的返回对象转换成数据字节响应给浏览器
         */
        @Override
        protected boolean supports(Class<?> clazz) {
            //判断父类是否为BaseKeyTopReq,如果是则使用该转换器
            if(clazz.getSuperclass() == BaseKeyTopReq.class){
                return true;
            }
            return false;
        }
        /**
         *解析请求的参数
         */
        @Override
        protected BaseKeyTopReq readInternal(Class<? extends BaseKeyTopReq> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
           //获取body信息
            InputStream is=inputMessage.getBody();
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            StringBuilder stringBuilder = new StringBuilder();
            br.lines().forEach(item->stringBuilder.append(item));
            logger.info("科托解密之前数据:"+stringBuilder.toString());
            JSONObject jsonObject = JSON.parseObject(stringBuilder.toString());
            String data = jsonObject.getString("data");
            //解密
            try {
                String desString = ThreeDESUtil.getDesString(data,ktkey,ktiv);
                logger.info("科托解密之后数据:"+desString);
                //将解密出来的信息转换为java对象,注意该对象必须继承BaseKeyTopReq
                return JSONObject.parseObject(desString,clazz);
            } catch (Exception e) {
                logger.error("科托解密失败",e);
                throw new BizException(ErrorType.DECODE_ERROR);
            }
        }
        /**
         * 响应给对象的参数
         * 将方法被@ResponseBody注解的返回对象转换成数据字节响应给浏览器
         */
        @Override
        protected void writeInternal(BaseKeyTopReq baseKeyTopReq, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    
        }
    }
    
    • 配置转换器
    @Configuration
    @EnableWebMvc
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
        @Value("${keytop.key}")
        private String ktkey;
        @Value("${keytop.iv}")
        private String ktiv;
        
        /**
         * 扩展消息转换器
         * 注意不能使用configureMessageConverters方法,使用configureMessageConverters方法,则只包含你新增的,springmvc默认的消息转换器没有了。
         * @param converters
         */
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            //增加科托消息转换器
            KeyTopMsgConverter converter  = new KeyTopMsgConverter(MediaType.APPLICATION_JSON,ktkey,ktiv);
            converters.add(0,converter);//将自定义的设置为优先级最高
        }
    }
    

    使用测试

    例如有个接口PostFreeParkingSpace,data字段信息为:plateNo,json格式。
    则可以创建一个PostFreeParkingSpaceReq对象,继承BaseKeyTopReq。

    //响应接口对应的对象
    public class PostFreeParkingSpaceReq extends BaseKeyTopReq {
        private String plateNo;
    
        public String getPlateNo() {
            return plateNo;
        }
    
        public void setPlateNo(String plateNo) {
            this.plateNo = plateNo;
        }
    }
    

    则接口调用方,发送的body体数据为:{"data":"DkTwRsUUza33A8/TvrocXI3r+Az1T7bt"},经过消息转换器解密(只解密data内容)之后为:{"plateNo":"A12345"},然后将该json字符串转换为java对象。
    则在controller中入参对象里面就有值了。

    • controller
    @RestController
    @RequestMapping("/keytop")
    public class KeyTopController {
    
        private static final Logger logger = LoggerFactory.getLogger(PubParkingController.class);
    
        @RequestMapping(value = "/PostFreeParkingSpace", method = RequestMethod.POST)
        public String PostFreeParkingSpace(@RequestBody PostFreeParkingSpaceReq spaceReq) {
            logger.info("科托空闲车位上报:" + JSON.toJSONString(spaceReq));
            /**此时从入参对象中获取的plateNo值则为A12345,已经解密完且转换成了对应的实体对象*/
            return spaceReq.getPlateNo();
        }
    
    }
    

    相关文章

      网友评论

          本文标题:springmvc 自定义消息转换器完整例子

          本文链接:https://www.haomeiwen.com/subject/futjhqtx.html