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<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();
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 亲人节的那天是象征爱情的宣言 让我们感动苍天给彼此一个开始的起点 爱你的危险,爱你的无言 那是我们许下的诺言 看着...
    不爱说话的话阅读 210评论 0 0
  • 大西瓜啊啊啊阅读 148评论 0 0
  • 刚刚在微博上看到一条的视频上了微博热搜,我是被它的名字吸引过去的,标题是《90后男生独住4层别墅,美得像仙境》。 ...
    那木可爱阅读 282评论 1 2