package com.company.project.configurer; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.company.project.common.interceptor.AllowCrossDomainInterceptor; import com.company.project.common.interceptor.ResponseResultInterceptor; import com.company.project.common.result.PlatformResult; import com.company.project.common.util.IpUtil; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 全局定制化Spring Boot的MVC特性 * * @author lerry */ @Configuration public class MvcConfigurer implements WebMvcConfigurer { private final Logger logger = LoggerFactory.getLogger(MvcConfigurer.class); /** * 当前激活的配置文件 */ @Value("${spring.profiles.active}") private String env; private String apiUri = "/**"; /** * 响应结果控制拦截 */ @Autowired private ResponseResultInterceptor responseResultInterceptor; /** * 跨域配置拦截器 */ @Autowired private AllowCrossDomainInterceptor allowCrossDomainInterceptor; @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //使用阿里 FastJson 作为JSON MessageConverter FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); //保留空的字段 config.setSerializerFeatures(SerializerFeature.WriteMapNullValue); // 按需配置,更多参考FastJson文档 converter.setFastJsonConfig(config); converter.setDefaultCharset(Charset.forName("UTF-8")); converters.add(converter); } /** * 添加拦截器 * * @param interceptorRegistry */ @Override public void addInterceptors(InterceptorRegistry interceptorRegistry) { //跨域拦截 interceptorRegistry.addInterceptor(allowCrossDomainInterceptor).addPathPatterns(apiUri); //响应结果控制拦截 interceptorRegistry.addInterceptor(responseResultInterceptor).addPathPatterns(apiUri); //接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。 //开发环境忽略签名认证 if (!"dev".equals(env)) { interceptorRegistry.addInterceptor(new HandlerInterceptorAdapter() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //验证签名 boolean pass = validateSign(request); if (pass) { return true; } else { logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}", request.getRequestURI(), IpUtil.getRealIp(request), JSON.toJSONString(request.getParameterMap())); PlatformResult result = new PlatformResult(); result.setCode(HttpStatus.UNAUTHORIZED.value()).setMessage("签名认证失败"); responseResult(response, result); return false; } } }); } } @Override public void addResourceHandlers(ResourceHandlerRegistry registry){ registry.addResourceHandler("/**") .addResourceLocations("classpath:/META-INF/resources/") .setCachePeriod(0); } private void responseResult(HttpServletResponse response, PlatformResult result) { response.setCharacterEncoding("UTF-8"); response.setHeader("Content-type", "application/json;charset=UTF-8"); response.setStatus(200); try { response.getWriter().write(JSON.toJSONString(result)); } catch (IOException ex) { logger.error(ex.getMessage()); } } /** * 一个简单的签名认证,规则: * 1. 将请求参数按ascii码排序 * 2. 拼接为a=value&b=value...这样的字符串(不包含sign) * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较 */ private boolean validateSign(HttpServletRequest request) { //获得请求签名,如sign=19e907700db7ad91318424a97c54ed57 String requestSign = request.getParameter("sign"); if (StringUtils.isEmpty(requestSign)) { return false; } List<String> keys = new ArrayList<>(request.getParameterMap().keySet()); //排除sign参数 keys.remove("sign"); //排序 Collections.sort(keys); StringBuilder sb = new StringBuilder(); //拼接字符串 for (String key : keys) { sb.append(key).append("=").append(request.getParameter(key)).append("&"); } String linkString = sb.toString(); //去除最后一个'&' linkString = StringUtils.substring(linkString, 0, linkString.length() - 1); //密钥,自己修改 String secret = "Potato"; //混合密钥md5 String sign = DigestUtils.md5Hex(linkString + secret); //比较 return StringUtils.equals(sign, requestSign); } }