【spring boot】RestTemplate 链接带签名post请求 400 bad request

由于项目需要从服务端对第三方发起请求,而且第三方没有提供SDK的情况下,只能根据对方api文档发送请求了,对方接口的格式是:地址+签名,post请求上送具体参数的方式去请求对方服务。

背景

很简单的一个需求,然而开始就卡住了,在Apifox调用能正常返回数据,而一用restTemplate去请求,就报400错误。

org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request
	at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:79)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:122)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:102)
	at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:778)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:736)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670)
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:579)
...

Apifox能调通,而代码调不通,不外乎代码创建起来的https请求有问题

分析

本来没打算细究,出错了打算改用OkHttpClient试一下,还真调通了

    private void action(String uri){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = null;
        body = RequestBody.create(MediaType.parse("application/json"), data);// data是jsonObject转的String数据
        Request request = new Request.Builder().url(uri)
                .method("POST", body)
                .addHeader("Content-Type", "application/json")
                .build();
        try {
            Response execute = client.newCall(request).execute();
            System.out.println(execute);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

感觉有点离谱,所以直接用抓包工具(这里用的是fiddler,刚学着用,踩了点坑)抓包一下

POST https://xxx/xx/request?AccessKeyId=xxx&Expires=xx&Signature=xxx&Timestamp=2023-12-11T03%253A39%253A36Z HTTP/1.1
Accept: application/json, application/*+json
Content-Type: application/json;charset=UTF-8
User-Agent: Java/1.8.0_221
Host: xxxxx
Connection: keep-alive
Content-Length: xx

{"data":"0"}

返回数据是

HTTP/1.1 400 Bad Request
Date: Mon, 11 Dec 2023 03:39:38 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive

9d
{"code":"","message":"日期格式错误,请使用yyyy-MM-ddTHH:mm:ssZ格式"}
0

很明显的错误提示“日期格式错误,请使用yyyy-MM-ddTHH:mm:ssZ格式”,回头看下上送的报文是“Timestamp=2023-12-11T03%253A39%253A36Z”,虽然不怎么熟悉url的编码解码,但是很明显%253A实际上是%3A既“:”,所以是因为时间被双重加密了,因为在拼接url的时候,我已经手动给时间进行了URL编码

URLEncoder.encode(String.valueOf(time), "UTF-8")

所以去掉后再来

POST https://xxx/xx/request?AccessKeyId=xxx&Expires=xx&Signature=***&Timestamp=2023-12-11T08:25:21Z HTTP/1.1
Accept: application/json, application/*+json
Content-Type: application/json;charset=UTF-8
User-Agent: Java/1.8.0_221
Host: xxxxx
Connection: keep-alive
Content-Length: xx

{"data":"0"}

返回数据

HTTP/1.1 401 Unauthorized
Date: Mon, 11 Dec 2023 08:25:21 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive

6c
{"error":{"code":"AuthFailure","message":"签名错误"}}
0

玩我呢…我不格式化它也不格式化,只能断点分析了。发现RestTemplate这里对url进行了处理

	@Override
	@Nullable
	public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
		// 这里getUriTemplateHandler调用了一个默认的处理器对url进行了处理
		URI expanded = getUriTemplateHandler().expand(url, uriVariables);
		return doExecute(expanded, method, requestCallback, responseExtractor);
	}

解决

所以,只要改变这个处理器就可以解决问题了

    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory();
        // 这里选择了不处理而是自己手动去处理编码了,所以就不再会出现之前的问题了
        uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
        restTemplate.setUriTemplateHandler(uriFactory);
        // ...
        return restTemplate;
    }

总结

很多时候,其实在编码的时候会下意识自信自己编写的没有问题, 以至于在调试的时候很难去发现问题点(开始的时候,其实我先断点了请求,发现请求是成功出去了,也没能关注到%253A的问题,一个劲的根据其他文章说的协议、请求头什么的问题在尝试),所以当发现问题但又自认为自己没有问题的时候,不妨换个角度换个方式,或者重新从头写一遍(有时候逻辑多的时候,其实也不要怕,一边写一边复盘会比自己只干看着分析还可能好一点)当然具体情况要结合自己实际去行动了。

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值