spring mvc 自定义注解ResponseEncryptBody、RequestDecryptBody统一处理加密、解密数据,供移动端使用的rest服务
起源
项目中spring web服务为移动端提供rest接口,很多接口需要加密、解密它们之间的数据传输,现老代码一般是在@Controller层每个request的开始和结尾处做的加密解密操作,这样的代码重复度很高,而且不利于更好的利用Spring框架的HttpMessageConverter达到java丰富类型的自动转换。
解决
Spring利用RequestBody、ResponseBody注解起到rest服务接口数据格式的自动转换,所以我只需要提供类似的注解RequestDecryptBody、ResponseEncryptBody注解,来达到我的目的。
/*
* Copyright 2016 https://github.com/sdcuike Inc.
* All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.doctor.springmvc.extend;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* json解密后的消息
*
* @author sdcuike
*
* Created At 2016年10月26日 下午8:56:27
*/
@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestDecryptBody {
}
/*
* Copyright 2016 https://github.com/sdcuike Inc.
* All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.doctor.springmvc.extend;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 返回json信息加密
*
* @author sdcuike
*
* Created At 2016年10月26日 下午8:54:08
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseEncryptBody {
}
为了处理自定义的注解,我们需要扩展RequestResponseBodyMethodProcessor:
/*
* Copyright 2016 https://github.com/sdcuike Inc.
* All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.doctor.springmvc.extend;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
/**
* 自定义注解处理器
*
* @author sdcuike
*
* Created At 2016年10月26日 下午9:44:25
*/
public class RequestDecryptResponseEncryptBodyMethodProcessor extends RequestResponseBodyMethodProcessor {
private final Logger log = LoggerFactory.getLogger(getClass());
public RequestDecryptResponseEncryptBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestDecryptBody.class);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseEncryptBody.class) ||
returnType.hasMethodAnnotation(ResponseEncryptBody.class));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
log.info("RequestURI:{}", webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI());
super.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
log.info("RequestURI:{}", webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI());
return super.readWithMessageConverters(webRequest, methodParam, paramType);
}
}
代码中:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestDecryptBody.class);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseEncryptBody.class) ||
returnType.hasMethodAnnotation(ResponseEncryptBody.class));
}
主要对自定义注解的处理支持。
RequestDecryptResponseEncryptBodyMethodProcessor起到了对自定义注解处理的作用,我们还需要扩展HttpMessageConverter,让RequestDecryptResponseEncryptBodyMethodProcessor用我们自定义的HttpMessageConverter对数据进行加密、解密处理。
利用fastjson中的FastJsonHttpMessageConverter4来做json的序列化操作,所以现在要对该类扩展处理:
package com.doctor.springmvc.extend;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4;
/**
*
* @author sdcuike
*
* Created At 2016年10月26日 下午11:53:32
*/
public class DecryptEncryptFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter4 implements InitializingBean {
private RequestDecryptResponseEncryptBodyProcessor requestDecryptResponseEncryptBodyProcessor;
public void setRequestDecryptResponseEncryptBodyProcessor(RequestDecryptResponseEncryptBodyProcessor requestDecryptResponseEncryptBodyProcessor) {
this.requestDecryptResponseEncryptBodyProcessor = requestDecryptResponseEncryptBodyProcessor;
}
public RequestDecryptResponseEncryptBodyProcessor getRequestDecryptResponseEncryptBodyProcessor() {
return requestDecryptResponseEncryptBodyProcessor;
}
public DecryptEncryptFastJsonHttpMessageConverter() {
super();
}
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
InputStream in = inputMessage.getBody();
FastJsonConfig fastJsonConfig = getFastJsonConfig();
if (requestDecryptResponseEncryptBodyProcessor != null) {
String input = requestDecryptResponseEncryptBodyProcessor.decryptRequestBody(inputMessage, fastJsonConfig.getCharset());
byte[] bytes = input.getBytes(fastJsonConfig.getCharset());
return JSON.parseObject(bytes, 0, bytes.length, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures());
}
return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures());
}
@Override
protected void writeInternal(Object obj, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
ByteArrayOutputStream outnew = new ByteArrayOutputStream();
FastJsonConfig fastJsonConfig = getFastJsonConfig();
if (requestDecryptResponseEncryptBodyProcessor != null) {
String jsonString = JSON.toJSONString(obj,
fastJsonConfig.getSerializeConfig(),
fastJsonConfig.getSerializeFilters(),
fastJsonConfig.getDateFormat(),
JSON.DEFAULT_GENERATE_FEATURE, //
fastJsonConfig.getSerializerFeatures());
obj = requestDecryptResponseEncryptBodyProcessor.encryptResponseBody(jsonString, headers, fastJsonConfig.getCharset());
}
int len = JSON.writeJSONString(outnew, //
fastJsonConfig.getCharset(), //
obj, //
fastJsonConfig.getSerializeConfig(), //
fastJsonConfig.getSerializeFilters(), //
fastJsonConfig.getDateFormat(), //
JSON.DEFAULT_GENERATE_FEATURE, //
fastJsonConfig.getSerializerFeatures());
headers.setContentLength(len);
OutputStream out = outputMessage.getBody();
outnew.writeTo(out);
outnew.close();
}
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
InputStream in = inputMessage.getBody();
FastJsonConfig fastJsonConfig = getFastJsonConfig();
if (requestDecryptResponseEncryptBodyProcessor != null) {
String input = requestDecryptResponseEncryptBodyProcessor.decryptRequestBody(inputMessage, fastJsonConfig.getCharset());
return JSON.parseObject(input.getBytes(fastJsonConfig.getCharset()), 0, input.length(), fastJsonConfig.getCharset(), clazz, fastJsonConfig.getFeatures());
}
return JSON.parseObject(in, fastJsonConfig.getCharset(), clazz, fastJsonConfig.getFeatures());
}
@Resource
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Override
public void afterPropertiesSet() throws Exception {
// 重新排列处理方法,不然map会先处理,从而加密不了数据
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(requestMappingHandlerAdapter.getReturnValueHandlers());
int requestResponseBodyMethodProcessorIndex = 0;
int requestDecryptResponseEncryptBodyMethodProcessorIndex = 0;
RequestDecryptResponseEncryptBodyMethodProcessor requestDecryptResponseEncryptBodyMethodProcessor = null;
for (int i = 0, length = handlers.size(); i < length; i++) {
HandlerMethodReturnValueHandler handler = handlers.get(i);
if (handler instanceof RequestDecryptResponseEncryptBodyMethodProcessor) {
requestDecryptResponseEncryptBodyMethodProcessor = (RequestDecryptResponseEncryptBodyMethodProcessor) handler;
requestDecryptResponseEncryptBodyMethodProcessorIndex = i;
} else if (handler instanceof RequestResponseBodyMethodProcessor) {
requestResponseBodyMethodProcessorIndex = i;
}
}
if (requestDecryptResponseEncryptBodyMethodProcessor != null) {
handlers.remove(requestDecryptResponseEncryptBodyMethodProcessorIndex);
handlers.add(requestResponseBodyMethodProcessorIndex + 1, requestDecryptResponseEncryptBodyMethodProcessor);
}
requestMappingHandlerAdapter.setReturnValueHandlers(handlers);
//
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(requestMappingHandlerAdapter.getArgumentResolvers());
RequestDecryptResponseEncryptBodyMethodProcessor requestDecryptResponseEncryptBodyMethodProcessor2 = null;
for (int i = 0, length = argumentResolvers.size(); i < length; i++) {
HandlerMethodArgumentResolver argumentResolver = argumentResolvers.get(i);
if (argumentResolver instanceof RequestDecryptResponseEncryptBodyMethodProcessor) {
requestDecryptResponseEncryptBodyMethodProcessor2 = (RequestDecryptResponseEncryptBodyMethodProcessor) argumentResolver;
requestDecryptResponseEncryptBodyMethodProcessorIndex = i;
} else if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
requestResponseBodyMethodProcessorIndex = i;
}
}
if (requestDecryptResponseEncryptBodyMethodProcessor2 != null) {
argumentResolvers.remove(requestDecryptResponseEncryptBodyMethodProcessorIndex);
argumentResolvers.add(requestResponseBodyMethodProcessorIndex + 1, requestDecryptResponseEncryptBodyMethodProcessor2);
}
requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
}
}
json的处理主要对方法覆盖,添加对加密和解密的处理操作,该功能主要由RequestDecryptResponseEncryptBodyProcessor类处理:
/*
* Copyright 2016 https://github.com/sdcuike Inc.
* All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.doctor.springmvc.extend;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
/**
* @author sdcuike
*
* Created At 2016年10月26日 下午11:59:07
*/
public abstract class RequestDecryptResponseEncryptBodyProcessor {
protected final Logger log = LoggerFactory.getLogger(getClass());
public final String decryptRequestBody(HttpInputMessage inputMessage, Charset charset) throws IOException {
InputStream inputStream = inputMessage.getBody();
String input = IOUtils.toString(inputStream, charset);
HttpHeaders httpHeaders = inputMessage.getHeaders();
return doDecryptRequestBody(input, httpHeaders, charset);
}
public final String encryptResponseBody(String input, HttpHeaders httpHeaders, Charset charset) {
return doEncryptResponseBody(input, httpHeaders, charset);
}
protected String doDecryptRequestBody(String input, HttpHeaders httpHeaders, Charset charset) {
return input;
}
protected String doEncryptResponseBody(String input, HttpHeaders httpHeaders, Charset charset) {
return input;
}
}
该类用了模版方法设计,我们只需要继承该类,覆盖加密、解密相关方法即可。
最后,我们需要对xml配置做处理,添加自己的方法和返回值处理扩展类:
<bean
id="requestDecryptResponseEncryptBodyProcessorImpl"
class="com.doctor.springmvc.demo.controller.RequestDecryptResponseEncryptBodyProcessorImpl" />
<bean
id="decryptEncryptFastJsonHttpMessageConverter"
class="com.doctor.springmvc.extend.DecryptEncryptFastJsonHttpMessageConverter"
p:requestDecryptResponseEncryptBodyProcessor-ref="requestDecryptResponseEncryptBodyProcessorImpl" />
<bean
id="requestDecryptResponseEncryptBodyMethodProcessor"
class="com.doctor.springmvc.extend.RequestDecryptResponseEncryptBodyMethodProcessor" >
<constructor-arg >
<list>
<ref bean="decryptEncryptFastJsonHttpMessageConverter"/>
</list>
</constructor-arg>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" />
</mvc:message-converters>
<mvc:argument-resolvers>
<ref bean="requestDecryptResponseEncryptBodyMethodProcessor" />
</mvc:argument-resolvers>
<mvc:return-value-handlers>
<ref bean="requestDecryptResponseEncryptBodyMethodProcessor" />
</mvc:return-value-handlers>
</mvc:annotation-driven>
<mvc:argument-resolvers>、<mvc:return-value-handlers> 的配置,就是为了让spring框架支持我们自定义的注解处理。
相关代码见:https://github.com/sdcuike/spring-web-demo/tree/master/src/main/java/com/doctor/springmvc/extend