問題描述:
最近在項目中對接第三方接口慌申,采用http協(xié)議陌选,post方法,協(xié)議類型:Content-Type: application/json;charset=utf-8蹄溉,將用戶名和密碼等信息放在header中咨油,用于驗證請求。將業(yè)務(wù)數(shù)據(jù)放到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 的自定義消息轉(zhuǎn)換器法瑟,在消息轉(zhuǎn)換器中先解密囱晴,然后將報文轉(zhuǎn)換為對應(yīng)的java對象,controller入?yún)⒅苯邮莏ava對象瓢谢,這樣校驗用戶名密碼和解密就可以單獨處理了畸写。
驗證用戶名和密碼,使用攔截器實現(xiàn)
因為用戶名和密碼放到了header中氓扛,可以在攔截器中獲取請求頭枯芬,判斷用戶名和密碼是否正確。
- 創(chuàng)建攔截器
@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);
//根據(jù)接口要求返回錯誤信息
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體解密,轉(zhuǎn)換為java對象
例如有個接口的data字段為:{“data”:{"platno":"A1234"}},獲取到的參數(shù)為加密之后的:{"data":"DkTwRsUUza33A8/TvrocXI3r+Az1T7bt"}蒜埋。
- 創(chuàng)建消息轉(zhuǎn)換器
為了使創(chuàng)建的消息轉(zhuǎn)換器只轉(zhuǎn)換本次業(yè)務(wù)新增的接口淫痰,創(chuàng)建一個請求基類bean對象,沒有任何字段整份,只是實現(xiàn)Serializable接口待错,作為其他業(yè)務(wù)的父類,例如:BaseKeyTopReq
public class BaseKeyTopReq implements Serializable{
}
創(chuàng)建消息轉(zhuǎn)換器如下:
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支持
* 會調(diào)用 readInternal 將http消息 轉(zhuǎn)換成方法中被@RequestBody注解的參數(shù)
* 會調(diào)用writeInternal 將被@ResponseBody注解的返回對象轉(zhuǎn)換成數(shù)據(jù)字節(jié)響應(yīng)給瀏覽器
*/
@Override
protected boolean supports(Class<?> clazz) {
//判斷父類是否為BaseKeyTopReq烈评,如果是則使用該轉(zhuǎn)換器
if(clazz.getSuperclass() == BaseKeyTopReq.class){
return true;
}
return false;
}
/**
*解析請求的參數(shù)
*/
@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("科托解密之前數(shù)據(jù):"+stringBuilder.toString());
JSONObject jsonObject = JSON.parseObject(stringBuilder.toString());
String data = jsonObject.getString("data");
//解密
try {
String desString = ThreeDESUtil.getDesString(data,ktkey,ktiv);
logger.info("科托解密之后數(shù)據(jù):"+desString);
//將解密出來的信息轉(zhuǎn)換為java對象火俄,注意該對象必須繼承BaseKeyTopReq
return JSONObject.parseObject(desString,clazz);
} catch (Exception e) {
logger.error("科托解密失敗",e);
throw new BizException(ErrorType.DECODE_ERROR);
}
}
/**
* 響應(yīng)給對象的參數(shù)
* 將方法被@ResponseBody注解的返回對象轉(zhuǎn)換成數(shù)據(jù)字節(jié)響應(yīng)給瀏覽器
*/
@Override
protected void writeInternal(BaseKeyTopReq baseKeyTopReq, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
}
- 配置轉(zhuǎn)換器
@Configuration
@EnableWebMvc
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Value("${keytop.key}")
private String ktkey;
@Value("${keytop.iv}")
private String ktiv;
/**
* 擴展消息轉(zhuǎn)換器
* 注意不能使用configureMessageConverters方法,使用configureMessageConverters方法讲冠,則只包含你新增的瓜客,springmvc默認(rèn)的消息轉(zhuǎn)換器沒有了。
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//增加科托消息轉(zhuǎn)換器
KeyTopMsgConverter converter = new KeyTopMsgConverter(MediaType.APPLICATION_JSON,ktkey,ktiv);
converters.add(0,converter);//將自定義的設(shè)置為優(yōu)先級最高
}
}
使用測試
例如有個接口PostFreeParkingSpace竿开,data字段信息為:plateNo谱仪,json格式。
則可以創(chuàng)建一個PostFreeParkingSpaceReq對象否彩,繼承BaseKeyTopReq疯攒。
//響應(yīng)接口對應(yīng)的對象
public class PostFreeParkingSpaceReq extends BaseKeyTopReq {
private String plateNo;
public String getPlateNo() {
return plateNo;
}
public void setPlateNo(String plateNo) {
this.plateNo = plateNo;
}
}
則接口調(diào)用方,發(fā)送的body體數(shù)據(jù)為:{"data":"DkTwRsUUza33A8/TvrocXI3r+Az1T7bt"}胳搞,經(jīng)過消息轉(zhuǎn)換器解密(只解密data內(nèi)容)之后為:{"plateNo":"A12345"}卸例,然后將該json字符串轉(zhuǎn)換為java對象称杨。
則在controller中入?yún)ο罄锩婢陀兄盗恕?/p>
- 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));
/**此時從入?yún)ο笾蝎@取的plateNo值則為A12345肌毅,已經(jīng)解密完且轉(zhuǎn)換成了對應(yīng)的實體對象*/
return spaceReq.getPlateNo();
}
}