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

问题描述:
最近在项目中对接第三方接口,采用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 {
    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 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> 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 自定义消息转换器完整例子)