SpringMvc擴充套件自定義註解實現web加密解密 不侵入業務程式碼
阿新 • • 發佈:2019-01-03
SpringMvc擴充套件自定義註解實現web解密 不侵入業務程式碼(1)
1.需求
在專案中spring web服務為外部APP提供rest介面,為了 安全考慮 一般會加入自己的驗證,常規 的簽名+資料加密, 當然一個好的架構師或者負責人會在專案初期會考慮到的問題現老程式碼一般是在@Controller層每個request的開始和結尾處做的加密解密操作,這樣的程式碼重複度很高,而且不利於更好的利用Spring框架的HttpMessageConverter達到java豐富型別的自動轉換。因為APP\傳到後臺是加密後的資料。無法做到自動拆箱,所以說我們要在接受引數之前進行解密操作,在返回資料的時候進行加密操作!
2.實現方式
- 攔截器實現
- 切面攔截實現
- 自定義註解實現
- 1考慮前兩種的效能,以及複用性本文采用 自定義註解去實現,當然這只是其中一部分原因。
- 2是看起來比較厲害對吧。(程式設計師都是騷的不行滴。O(∩_∩)O)
3.具體程式碼
- 包命名
- 我認為一份好的程式碼以及要有規矩,當然我是工作不太久,但是我在儘量對我的程式碼和各種命令寫好。
- com.xxx.open
- com.xxx.open.common.annotation -------------------- 自定義註解包
- com.xxx.common.container -------------------- 容器產量
- com.xxx.common.entity -------------------- 實體類
- com.xxx.common.exception -------------------- 自定義異常
- com.xxx.common.support -------------------- 對自定義註解算是的實現吧
- com.xxx.util -------------------- 加密解密工具類
2.自定義註解實現解密
#自定義自己的路徑
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author 蔡高情
* @Description //接收資料 自定義註解解密
* @Date 11:43 2018/7/9 0009
**/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestBodyDecrypt {
String value() default "";
boolean required() default true;
}
在資料之前進行解密自動拆箱,但是spring自帶的引數解析器不具有這種功能,只能嘗試著用自定義引數解析器去解決。
自定義解析器需要實現HandlerMethodArgumentResolver介面,HandlerMethodArgumentResolver介面包含兩個介面
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
public interface HandlerMethodArgumentResolver {
//supportsParameter:用於判定是否需要處理該引數分解,返回true為需要,並會去呼叫下面的方法
boolean supportsParameter(MethodParameter var1);
//真正用於處理引數分解的方法,返回的Object就是controller方法上的形參物件。
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
}
package com.caigaoqing.tech.common.support;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.caigaoqing.tech.common.entity.JSONObjectWrapper;
import com.caigaoqing.tech.common.annotation.RequestBodyDecrypt;
import com.caigaoqing.tech.common.exception.SharechargerOpenException;
import com.caigaoqing.tech.util.encrypt.AesCBC;
import com.caigaoqing.tech.util.encrypt.GetRequestDataUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* @Author 蔡高情
* @Description 自定義HandlerMethodArgumentResolver解析
* @Date 12:36 2018/7/9 0009
* @Param
* @return
**/
@Slf4j
public class RequestBodyDecryptArgumentResolver implements HandlerMethodArgumentResolver {
//判斷是否支援要轉換的引數型別
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBodyDecrypt.class);
}
//當支援後進行相應的轉換
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception{
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
//獲取全部的所有引數
Map<String, String[]> parMap=request.getParameterMap();
//獲取加密的postBody
String encryBody= GetRequestDataUtil.getEncryBody(request);
//進行解密操作返回解密後的書資料getRequestBody();
String body=getRequestBody(encryBody);
Object result;
log.debug("THE CLIENT POST BODY " +body);
//下面寫的是拆箱操作,其實應該還有很多方式,這裡舉個最為明白的吧
Class clazz = parameter.getParameterType();
if (StringUtils.isEmpty(body)) {
return null;
}
if (clazz.equals(List.class)){
result= JSON.parseArray(body);
}
// 利用fastjson轉換為對應的型別
if(JSONObjectWrapper.class.isAssignableFrom(parameter.getParameterType())){
result= new JSONObjectWrapper(JSON.parseObject(body));
}
else {
result= JSON.parseObject(body.toString(), parameter.getParameterType());
}
//如果有key則獲取key的值返回
String key=parameter.getParameterAnnotation(RequestBodyDecrypt.class).value();
if (StringUtils.isNotEmpty(key)){
JSONObject data= JSON.parseObject(body.toString());
//獲取json的key
result=data.get(key);
}
return result;
}
/**
* 獲取提交的post json
* 驗證加密
* @param
* @return
*/
private String getRequestBody(String encryBody) throws SharechargerOpenException ,Exception{
//這裡可以做appId,appk的校驗,這裡涉及到企業程式碼不寫了,用base64代替
//解析完後 的傳遞引數
//這裡可以動態獲取,我這寫死了
String data=AesCBC.decrypt(encryBody,"1234567812345678","1234567812345678");
return data;
}
}
把自定義的解析器加入到springmvc中
package com.sharecharger.open.common.support;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
public class RequestBodyWrapFactoryBean implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
/**
* 新增自定義解析器
* @param
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
RequestBodyDecryptArgumentResolver decorator = new RequestBodyDecryptArgumentResolver();
if (decorator instanceof HandlerMethodArgumentResolver) {
list.add(decorator);
}
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
4.注意事項
我程式碼寫的不好,還請大家多多見諒!!!!
這個註解對map的非常不友好。map會優先被springmvc的自帶解析器有限處理。在下節在說解決這個問題!
5.結果
著作權歸作者所有,轉載請標誌作者。2018/08/17
上面應該是接受引數 解密的
程式碼應該沒有問題的,具體檢視碼雲:
https://gitee.com/CaiGaoQing/open-api