diff --git a/PARAMS_RSA_DECRYPT_ENCRYPT.md b/PARAMS_RSA_DECRYPT_ENCRYPT.md new file mode 100644 index 0000000..b2b8003 --- /dev/null +++ b/PARAMS_RSA_DECRYPT_ENCRYPT.md @@ -0,0 +1,114 @@ +# 加密解密功能介绍 + + +#### 客户端解密(服务器端加密出参data) + +>API返回值的data部分会用私钥加密
+>1.先使用公钥解密得到解密字符串
+>2.1返回data为Map类型:先转成Map,然后ASCII码排序得到->result1=xxxxx&result2=xxxxx,再MD5的到sign
+>2.2返回data为List类型:先转成List,然后JSON化,再MD5的到sign
+>2.3返回data为字符串或基础类型:先转成String,再MD5的到sign
+ + +>客户端使用公钥解密 + +正常api返回值 +```json +{ + "code": "S", + "data":"xxxxx", + "msg": [ + "操作成功" + ] +} +``` + +加密后api返回值 +```json +{ + "code": "S", + "data":"LZ6ylSuKj36Pd5_TGfHmmJUifKQq_BLLD_CRyw17Z6y9jfcjpK.....", + "sign":"dee2f5af75b50f99b16726784230afeb", + "msg": [ + "请求成功" + ] +} +``` + +解析代码 +``` + //map + String mapData = "cEPfMp7-rim76XFdbNDSIzDRXyHySpz0VOZP6HC8U-JPl-ZxNZKp6ethEQWWITcuUPzIhp4fHiGKNsHA7F6OxCxibpMLj5-ZsgJJvcczw8Liens5kYgciRF1UziR3LFy6vybN9H1CJnqXaddbl3t_41P-_1l5Ev7YYa8woWp7ulaRPeTCDjohEpmx2Vi6aPSrm3hjjmitkD9gb0O6vFDNnclyNhFepKV3oh93tNv50sEQQ_QSBUXSHUtCnhTiBX8VsRX3h58F2tie7bG8VSk-6KFuXI07OiqFZSNpcwDOuq-GfMlEfPL3pX-gYhoOORPNClRlQHwyfHXBJly3gRtNVpVksHWQjr1xutWgYfwRjQPHBHNZwfx4E0XoCTuz9qH1CzFmmz68i63GzCM286zJ-J26MkiTDO1zH4jhglo38tnzz9HLeDcbbCuJg1jzkvpFiWamM-6odWhtCg65BS1tJJVWg023kWygZMu5Ebrm5WBbbatN87_K5zn211tFpKwRq2oVjO_AfJRY90WlQGEIHnzZNz_cf8mAjlmilHDuNdjYlj3axTUqLfgLDVaIkasREnjMI7oe8oAtG2ju2aq-xSAQZ_U-7-rsyBpoy0jnwRmlyUxhXgIX0zrfBQNXEjzPtg-iJ14R5qz1iOAJL7NtQQeuYngGTj6msDlKGEd_MQTLAFDbpiVPwWX00jLT1Ll3_zhivpPCUAmC8Yz58khkqrqi4FdIxJTDkxd0PFOBH8DYicF7ls4UdOHT24mAKDwUF_TfZ32oiiKSzCD9MJB8GEXjzx7tDFok-HsdOjI6ZnSUJCOTj3wne2E6_a8Gq2_vp5CWyW12wthJbH79aa7JVfy5cx-cZmNid7oCe54KYclz1tdUgLPCQ1ajsEevbRJ_NBkTmY2wAmUpHODeocDaYt_2AwAU2DLiv2uZuaVszNSUy593Zrzxq5AaY-oWbEeD24SyEWJObJtz5knYzr4NxjZShcjx9ezwiwkRZMtLZpA_cCPFAK1nOrN8zHCOZquS17CCSLDySLvGbxNqYeBa_lGSq8cQuQo8yybd1WkuLKUjUiJecmH2XcZNTPCtdRe0eLlRtk5928AQGsQugwSig"; + String sign = StrUtil.toMD5( + StrUtil.getSignContent( + JsonUtil.json2Obj(mapData, Map.class) + ) + ) + //list + String listData = "Sf_FO8YC9EUNTeM0n9EVuDzwvLz3DcxOuG4-5UZ9486lLHAx7IOuAhPgVHpQGsQiqJ7Y3fTaWFr6rRFPL12rVg"; + String sign = StrUtil.toMD5( + JsonUtil.obj2Json( + JsonUtil.json2Obj(listData, List.class) + ) + ) + //其他 + String strData = "K9Zyg82WDvIApFmXTxPwjQw_VA041jfBcxMIP6jpMM6xWe1XajGf3__7DqSLrS9MwCra9cYkidcjVJAKZn9cmQ"; + strData = RSA.publicDecrypt(strData, RSA.getPublicKey(properties.getPublicKey())); + String sign =StrUtil.toMD5(JsonUtil.json2Obj(strData, String.class); + +``` + + +#### 客户端加密 (服务器端解密入参data) + +>1.客户端先使用公钥加密,参数为Map类型
+>然后ASCII码排序得到新的参数->result1=xxxxx&result2=xxxxx,再MD5的到sign
+>加密新的参数得到data(也可以对原map JSON化后加密) +> +>2.API接收到json参数,解析为ParamsPO
+>ParamsPO.data部分进行解密操作,解密后得到原始参数(result1=xxxxx&result2=xxxxx)做MD5校验 + +原始参数 +``` +Map map = new HashMap(); +map.put("page", 5); +map.put("size", 16); +map.put("content", "定制榻榻米垫竹编客厅茶几垫卧室地毯竹地毯飘窗垫日式榻榻米地毯"); + +//对参数ASCII码排序 +String data = StrUtil.getSignContent(map); +//封装请求参数 +ParamsPO params = new ParamsPO(); +params.setSign(StrUtil.toMD5(datas)); +params.setData(RSA.publicEncrypt(datas, RSA.getPublicKey(properties.getPublicKey()))); +``` + +加密后的请求参数 +```json +{ + "data":"pe0V05nr5bfUp7c/JL1b/b6qJHipA5Qx8vM8BRryu3k=", + "sign":"dee2f5af75b50f99b16726784230afeb" +} +``` + + +### 配置 +1. 服务器配置私钥 +``` +yexuejc.http.encrypt.private-key=私钥 +yexuejc.http.encrypt.encrypt=true //加密:默认false +yexuejc.http.encrypt.decrypt=true //解密:默认false +``` + +2.客户端请求 +``` +request:POST +header: +X-User-Agent:token +Content-Type:application/json +body: +{ +"datas":"pe0V05nr5bfUp7c/JL1b/b6qJHipA5Qx8vM8BRryu3k=", +"sign":"123456789" +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 78a1c2a..d1d1e95 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ #### 项目介绍 基于springboot maven 封装可继承基础工程 -内含parent和base工程可分开使用 -parent:版本封装 +内含parent和base工程可分开使用
+parent:版本封装
base:功能封装 #### 引用 @@ -95,4 +95,7 @@ pom.xml #### 版本更新 -[更新记录](UPDATE.md) \ No newline at end of file +[更新记录](UPDATE.md) +
+[1.0.5新增 针对API请求安全解决方案](PARAMS_RSA_DECRYPT_ENCRYPT.md)
+[1.0.5新增 加密功能](PARAMS_RSA_DECRYPT_ENCRYPT.md) \ No newline at end of file diff --git a/UPDATE.md b/UPDATE.md index 3e42d42..753c6ad 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,7 +1,16 @@ uselaw-base 更新内容 ------------------- -#### version :1.0.4 +#### version :1.0.5 +**time:** 2018-5-4 09:54:18
+**branch:** master
+**update:**
+> [使用加密解密](PARAMS_RSA_DECRYPT_ENCRYPT.md) +> +>1.增加json入参解密、出参加密 +# + +##### version :1.0.4 **time:** 2018-5-4 09:54:18
**branch:** master
**update:**
diff --git a/pom.xml b/pom.xml index 218358f..a42afa9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.yexuejc.springboot yexuejc-springboot-parent - 1.0.4 + 1.0.5 pom ${project.artifactId} @@ -35,7 +35,7 @@ 1.2.0 3.4.2 - 1.0.0 + 1.1.0 0.7.0 1.1.46 1.10 diff --git a/yexuejc-springboot-base/pom.xml b/yexuejc-springboot-base/pom.xml index 57e560a..b9d2f25 100644 --- a/yexuejc-springboot-base/pom.xml +++ b/yexuejc-springboot-base/pom.xml @@ -9,7 +9,7 @@ com.yexuejc.springboot yexuejc-springboot-parent - 1.0.4 + 1.0.5 @@ -22,9 +22,13 @@ + + + + - com.github.yexuejc - yexuejc-base + com.github.yexuejc + yexuejc-base @@ -74,6 +78,14 @@ jedis true + + + commons-io + commons-io + 2.6 + true + + org.springframework.boot spring-boot-starter-test diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/autoconfigure/WebAutoConfiguration.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/autoconfigure/WebAutoConfiguration.java index a34f318..9d1146e 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/autoconfigure/WebAutoConfiguration.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/autoconfigure/WebAutoConfiguration.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.yexuejc.base.http.Resps; import com.yexuejc.base.util.DateUtil; +import com.yexuejc.base.util.StrUtil; import com.yexuejc.springboot.base.filter.ValidationFilter; import com.yexuejc.springboot.base.filter.ValidationFilterProperties; import com.yexuejc.springboot.base.interceptor.LogInterceptor; @@ -19,14 +20,20 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import java.nio.charset.Charset; +import java.util.List; + /** * WebMvc相关配置 * @@ -45,6 +52,28 @@ public class WebAutoConfiguration extends WebMvcConfigurerAdapter { this.properties = properties; } + /******************************************编码部分*****************************************************/ + @Bean + public HttpMessageConverter responseBodyConverter() { + StringHttpMessageConverter converter = new StringHttpMessageConverter( + Charset.forName("UTF-8")); + return converter; + } + + @Override + public void configureMessageConverters( + List> converters) { + super.configureMessageConverters(converters); + converters.add(responseBodyConverter()); + } + + @Override + public void configureContentNegotiation( + ContentNegotiationConfigurer configurer) { + configurer.favorPathExtension(false); + } + /******************************************编码部分*****************************************************/ + /** * 添加拦截器 */ @@ -108,7 +137,7 @@ public class WebAutoConfiguration extends WebMvcConfigurerAdapter { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Resps jsonErrorHandler(Throwable e) { LogUtil.exceptionLogger.error(e.getMessage(), e); - return Resps.error(ERROR_MSG); + return Resps.error(StrUtil.setStr(e.getMessage(), ERROR_MSG)); } } } diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/GatewayException.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/GatewayException.java new file mode 100644 index 0000000..038a49c --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/GatewayException.java @@ -0,0 +1,24 @@ +package com.yexuejc.springboot.base.exception; + +/** + * 网关异常 + * + * @author: maxf + * @date: 2018/5/12 18:36 + */ +public class GatewayException extends RuntimeException { + + private static final long serialVersionUID = -2390195902982826130L; + /** + * 错误码 + */ + private String code = "E"; + + public GatewayException() { + super("数据错误"); + } + + public GatewayException(String message) { + super(message); + } +} diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java new file mode 100644 index 0000000..659bc24 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java @@ -0,0 +1,115 @@ +package com.yexuejc.springboot.base.filter; + +import com.yexuejc.base.pojo.ParamsPO; +import com.yexuejc.base.util.JsonUtil; +import com.yexuejc.base.util.StrUtil; +import com.yexuejc.springboot.base.exception.GatewayException; +import com.yexuejc.springboot.base.util.LogUtil; +import com.yexuejc.springboot.base.util.RSA; +import org.apache.commons.io.IOUtils; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.List; +import java.util.Map; + +/** + * 请求数据解密 + * + * @version 1.0.5 + * @ClassName: ParamsRequestBodyAdvice + * @Description: + * @author: maxf + * @date: 2018/5/12 22:49 + */ +@ControllerAdvice(basePackages = "com") +@EnableConfigurationProperties(RsaProperties.class) +public class ParamsRequestBodyAdvice implements RequestBodyAdvice { + + private final RsaProperties properties; + + public ParamsRequestBodyAdvice(RsaProperties properties) { + this.properties = properties; + } + + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + return true; + } + + @Override + public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) throws IOException { + if (properties.isDecrypt()) { + ParamsPO paramsPO = JsonUtil.json2Obj(IOUtils.toString(inputMessage.getBody(), "UTF-8"), ParamsPO.class); + //RSA解密 + try { + long t = System.currentTimeMillis(); + String data = new String( + RSA.privateDecrypt( + paramsPO.getData(), + RSA.getPrivateKey(properties.getPrivateKey()) + ) + ); + //md5 校验 + if (!StrUtil.toMD5(data).equals(paramsPO.getSign())) { + LogUtil.accessLogger.error("sign错误,请求内容:{}", JsonUtil.obj2Json(paramsPO)); + throw new GatewayException("sign错误"); + } + InputStream body = IOUtils.toInputStream(JsonUtil.obj2Json(StrUtil.parseUrlencoded(data)), "UTF-8"); + LogUtil.accessLogger.info("解密耗时:{}", System.currentTimeMillis() - t); + return new MyHttpInputMessage(inputMessage.getHeaders(), body); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new GatewayException("data错误"); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + throw new GatewayException("data错误"); + } catch (Exception e) { + e.printStackTrace(); + throw new GatewayException("data错误"); + } + } + return inputMessage; + } + + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + class MyHttpInputMessage implements HttpInputMessage { + private HttpHeaders headers; + + private InputStream body; + + public MyHttpInputMessage(HttpHeaders headers, InputStream body) throws Exception { + this.headers = headers; + this.body = body; + } + + @Override + public InputStream getBody() throws IOException { + return body; + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + } +} \ No newline at end of file diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java new file mode 100644 index 0000000..cca6bb6 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java @@ -0,0 +1,88 @@ +package com.yexuejc.springboot.base.filter; + +import com.yexuejc.base.http.Resps; +import com.yexuejc.base.util.JsonUtil; +import com.yexuejc.base.util.StrUtil; +import com.yexuejc.springboot.base.util.LogUtil; +import com.yexuejc.springboot.base.util.RSA; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.List; +import java.util.Map; + + +/** + * 返回数据加密 + * + * @version 1.0.5 + * @ClassName: ParamsResponseBodyAdvice + * @Description: + * @author: maxf + * @date: 2018/5/12 22:50 + */ +@ControllerAdvice(basePackages = "com") +@EnableConfigurationProperties(RsaProperties.class) +public class ParamsResponseBodyAdvice implements ResponseBodyAdvice { + + private final RsaProperties properties; + + public ParamsResponseBodyAdvice(RsaProperties properties) { + this.properties = properties; + } + + + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + if (returnType.getMethod().isAnnotationPresent(SerializedField.class)) { + //获取注解配置的包含和去除字段 + SerializedField serializedField = returnType.getMethodAnnotation(SerializedField.class); + //是否加密 + properties.setEncrypt(serializedField.encode()); + } + if (properties.isEncrypt()) { + if (body instanceof Resps) { + long t = System.currentTimeMillis(); + Resps resps = (Resps) body; + if (StrUtil.isNotEmpty(resps.getData())) { + String data = ""; + if (resps.getData() instanceof Map) { + data = StrUtil.getSignContent((Map) resps.getData()); + } else if (resps.getData() instanceof List) { + data = JsonUtil.obj2Json(resps.getData()); + } else if (resps.getData() instanceof String) { + data = (String) resps.getData(); + } else { + data = JsonUtil.obj2Json(resps.getData()); + } + resps.setSign(StrUtil.toMD5(data)); + try { + resps.setData( + RSA.privateEncrypt(JsonUtil.obj2Json(resps.getData()), RSA.getPrivateKey(properties.getPrivateKey())) + ); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + LogUtil.accessLogger.error("出参加密错误,进行明文出参{}。\n异常信息:{}", JsonUtil.obj2Json(resps), e.getMessage()); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + LogUtil.accessLogger.error("出参加密错误,进行明文出参{}。\n异常信息:{}", JsonUtil.obj2Json(resps), e.getMessage()); + } + } + LogUtil.accessLogger.info("加密耗时:{}", System.currentTimeMillis() - t); + } + } + return body; + } +} \ No newline at end of file diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/RsaProperties.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/RsaProperties.java new file mode 100644 index 0000000..6a819c6 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/RsaProperties.java @@ -0,0 +1,68 @@ +package com.yexuejc.springboot.base.filter; + +import com.yexuejc.base.util.JsonUtil; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 加密,解密相关配置 + * + * @version 1.0.5 + * @author: maxf + * @date: 2018/5/12 18:14 + */ +@ConfigurationProperties(prefix = "yexuejc.http.encrypt") +public class RsaProperties { + /** + * 私钥 + */ + private String privateKey = ""; + /** + * 公钥 + */ + private String publicKey = ""; + /** + * 是否解密 + */ + private boolean decrypt = false; + /** + * 是否加密 + */ + private boolean encrypt = false; + + @Override + public String toString() { + return JsonUtil.obj2Json(this); + } + + public boolean isDecrypt() { + return decrypt; + } + + public void setDecrypt(boolean decrypt) { + this.decrypt = decrypt; + } + + public boolean isEncrypt() { + return encrypt; + } + + public void setEncrypt(boolean encrypt) { + this.encrypt = encrypt; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } +} diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/SerializedField.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/SerializedField.java new file mode 100644 index 0000000..ea29f88 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/SerializedField.java @@ -0,0 +1,27 @@ +package com.yexuejc.springboot.base.filter; + +import org.springframework.web.bind.annotation.Mapping; + +import java.lang.annotation.*; + +/** + * 指定出参加密 + * + * @version 1.0.5 + * @ClassName: SerializedField + * @Description: + * @author: maxf + * @date: 2018/5/12 22:51 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Mapping +public @interface SerializedField { + /** + * 是否加密 + * + * @return + */ + boolean encode() default true; +} \ No newline at end of file diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ValidationFilter.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ValidationFilter.java index 8b0f4df..6080f1c 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ValidationFilter.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ValidationFilter.java @@ -13,8 +13,16 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +/** + * 过滤器配置 + * + * @version 1.0.5 + * @ClassName: ValidationFilter + * @Description: + * @author: maxf + * @date: 2018/5/14 17:49 + */ public class ValidationFilter implements Filter { - ValidationFilterProperties properties; public ValidationFilter(ValidationFilterProperties properties) { @@ -26,6 +34,24 @@ public class ValidationFilter implements Filter { } + /** + * 请求安全 + * 解决方案: + * header头添加X-User-Agent:授权 + * 入参取时间戳,先MD5,然后进行RSA非对称加密 + * 按顺序解密: + * 1.防泄漏:RSA解密 + * 2.防篡改:md5参数校验 + * 3.防重复请求:时间戳(一分钟时间差) + * 4.防XSS攻击:header头添加X-User-Agent授权 + * 5.登录角色token授权 + * + * @param servletRequest + * @param servletResponse + * @param filterChain + * @throws IOException + * @throws ServletException + */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { @@ -46,6 +72,9 @@ public class ValidationFilter implements Filter { return; } } + /** + * stop1:header头 校验 + */ String xuserAgent = request.getHeader(RespsConsts.HEADER_X_USER_AGENT); if (xuserAgent == null || xuserAgent.length() == 0) { // 写日志 @@ -68,4 +97,6 @@ public class ValidationFilter implements Filter { public void destroy() { } + + } diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/pojo/PagerVO.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/pojo/PagerVO.java deleted file mode 100644 index a56749a..0000000 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/pojo/PagerVO.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.yexuejc.springboot.base.pojo; - -import com.yexuejc.base.pojo.BaseVO; -import com.yexuejc.base.util.JsonUtil; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -/** - * 分页 VO - * - * @author: maxf - * @date: 2018/3/28 14:23 - */ -public class PagerVO extends BaseVO { - - private static final long serialVersionUID = 3490440129554644587L; - - @NotNull - @Min(1L) - private Integer page = 1; - @NotNull - @Min(1L) - private Integer size = 20; - - public int getOffset() { - return (this.page - 1) * this.size; - } - - @Override - public String toString() { - return JsonUtil.obj2Json(this); - } - - public Integer getPage() { - return page; - } - - public void setPage(Integer page) { - this.page = page; - } - - public Integer getSize() { - return size; - } - - public void setSize(Integer size) { - this.size = size; - } -} diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/NetUtil.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/NetUtil.java index 364af15..1bf94f0 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/NetUtil.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/NetUtil.java @@ -2,6 +2,7 @@ package com.yexuejc.springboot.base.util; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.Enumeration; /** * 网络工具类 @@ -20,7 +21,6 @@ public class NetUtil { public final static String getIp(HttpServletRequest request) throws IOException { // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址 String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/RSA.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/RSA.java new file mode 100644 index 0000000..b4310d6 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/util/RSA.java @@ -0,0 +1,177 @@ +package com.yexuejc.springboot.base.util; + +import org.apache.commons.codec.binary.Base64; +import org.apache.tomcat.util.http.fileupload.IOUtils; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +public class RSA { + + public static final String CHARSET = "UTF-8"; + public static final String RSA_ALGORITHM = "RSA"; + + public static Map initKeys(int keySize) { + //为RSA算法创建一个KeyPairGenerator对象 + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]"); + } + + //初始化KeyPairGenerator对象,密钥长度 + kpg.initialize(keySize); + //生成密匙对 + KeyPair keyPair = kpg.generateKeyPair(); + //得到公钥 + Key publicKey = keyPair.getPublic(); + String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded()); + //得到私钥 + Key privateKey = keyPair.getPrivate(); + String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded()); + Map keyPairMap = new HashMap(); + keyPairMap.put("publicKey", publicKeyStr); + keyPairMap.put("privateKey", privateKeyStr); + + return keyPairMap; + } + + /** + * 得到公钥 + * + * @param publicKey 密钥字符串(经过base64编码) + * @throws Exception + */ + public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + //通过X509编码的Key指令获得公钥对象 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey)); + RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec); + return key; + } + + /** + * 得到私钥 + * + * @param privateKey 密钥字符串(经过base64编码) + * @throws Exception + */ + public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + //通过PKCS#8编码的Key指令获得私钥对象 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)); + RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); + return key; + } + + /** + * 公钥加密 + * + * @param data + * @param publicKey + * @return + */ + public static String publicEncrypt(String data, RSAPublicKey publicKey) { + try { + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength())); + } catch (Exception e) { + throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e); + } + } + + /** + * 私钥解密 + * + * @param data + * @param privateKey + * @return + */ + + public static String privateDecrypt(String data, RSAPrivateKey privateKey) { + try { + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET); + } catch (Exception e) { + throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e); + } + } + + /** + * 私钥加密 + * + * @param data + * @param privateKey + * @return + */ + + public static String privateEncrypt(String data, RSAPrivateKey privateKey) { + try { + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength())); + } catch (Exception e) { + throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e); + } + } + + /** + * 公钥解密 + * + * @param data + * @param publicKey + * @return + */ + + public static String publicDecrypt(String data, RSAPublicKey publicKey) { + try { + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET); + } catch (Exception e) { + throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e); + } + } + + private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) { + int maxBlock = 0; + if (opmode == Cipher.DECRYPT_MODE) { + maxBlock = keySize / 8; + } else { + maxBlock = keySize / 8 - 11; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] buff; + int i = 0; + try { + while (datas.length > offSet) { + if (datas.length - offSet > maxBlock) { + buff = cipher.doFinal(datas, offSet, maxBlock); + } else { + buff = cipher.doFinal(datas, offSet, datas.length - offSet); + } + out.write(buff, 0, buff.length); + i++; + offSet = i * maxBlock; + } + } catch (Exception e) { + throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e); + } + byte[] resultDatas = out.toByteArray(); + IOUtils.closeQuietly(out); + return resultDatas; + } + +} diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/ApplicationTest.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/ApplicationTest.java index 9c73893..551c371 100644 --- a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/ApplicationTest.java +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/ApplicationTest.java @@ -1,17 +1,104 @@ package com.yexuejc.springboot.base; +import com.yexuejc.base.pojo.ParamsPO; +import com.yexuejc.base.util.JsonUtil; +import com.yexuejc.base.util.StrUtil; +import com.yexuejc.springboot.base.filter.RsaProperties; +import com.yexuejc.springboot.base.util.RSA; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = ApplicationRun.class) public class ApplicationTest { + @Autowired + RsaProperties properties; @Test - public void contextLoads() { - System.out.printf("springboot test is runing"); + public void contextLoads() throws InvalidKeySpecException, NoSuchAlgorithmException { + Map map = new HashMap(); + map.put("page", 5); + map.put("size", 16); + map.put("content", "定制榻榻米垫竹编客厅茶几垫卧室地毯竹地毯飘窗垫日式榻榻米地毯"); + map.put("content2", map.get("content")); + map.put("content3", map.get("content")); + map.put("content4", map.get("content")); + map.put("content5", map.get("content")); + map.put("content6", map.get("content")); + + long t = System.currentTimeMillis(); + String datas = StrUtil.getSignContent(map); + System.out.println(datas); + ParamsPO params = new ParamsPO(); + params.setSign(StrUtil.toMD5(datas)); + params.setData(RSA.publicEncrypt(datas, RSA.getPublicKey(properties.getPublicKey()))); + System.out.println("耗时" + (System.currentTimeMillis() - t)); + System.out.println(JsonUtil.obj2Json(params)); + + //{"datas":"GfobmjZv8naQoy6Cy1DVVPQTmXoSkAXKdv8XvxBFCDYcY2khmpOQyiM8Jc5DEeBEoGC_JwyG_f89jRhcHRy4WQ", + // "sign":"d46b089cdea6ddbe3a747a27454ae090"} + } + + @Test + public void t2() throws InvalidKeySpecException, NoSuchAlgorithmException { +// String strData = "KrlXChF8LE94EEnycvbi8AygpaZiHKaXH_OmC5sGhGQlvYp1arNk6WW7yR7kAWMLugCS5TKf8FIiYXnyuI8vjA"; +// String strData = "MNqGKU5oYTXs77mnRgIkN_DkwBgNHUx3uiJ72QmTmyqkF9Rqf2m_q3mc4wTxLRjXLs8KaH_UExiXMhH0Xm63gA"; +// String strData = "HgTMgxEEFSr6zRLvZQ3U5CAKjJqwKf0lfruZTi32iWObkbJA5mHuOTKU4rXYkej4UsPfArYUA45GfxatwFoB4Q"; + String strData = "K9Zyg82WDvIApFmXTxPwjQw_VA041jfBcxMIP6jpMM6xWe1XajGf3__7DqSLrS9MwCra9cYkidcjVJAKZn9cmQ"; + + + String mapData = "cEPfMp7-rim76XFdbNDSIzDRXyHySpz0VOZP6HC8U-JPl-ZxNZKp6ethEQWWITcuUPzIhp4fHiGKNsHA7F6OxCxibpMLj5-ZsgJJvcczw8Liens5kYgciRF1UziR3LFy6vybN9H1CJnqXaddbl3t_41P-_1l5Ev7YYa8woWp7ulaRPeTCDjohEpmx2Vi6aPSrm3hjjmitkD9gb0O6vFDNnclyNhFepKV3oh93tNv50sEQQ_QSBUXSHUtCnhTiBX8VsRX3h58F2tie7bG8VSk-6KFuXI07OiqFZSNpcwDOuq-GfMlEfPL3pX-gYhoOORPNClRlQHwyfHXBJly3gRtNVpVksHWQjr1xutWgYfwRjQPHBHNZwfx4E0XoCTuz9qH1CzFmmz68i63GzCM286zJ-J26MkiTDO1zH4jhglo38tnzz9HLeDcbbCuJg1jzkvpFiWamM-6odWhtCg65BS1tJJVWg023kWygZMu5Ebrm5WBbbatN87_K5zn211tFpKwRq2oVjO_AfJRY90WlQGEIHnzZNz_cf8mAjlmilHDuNdjYlj3axTUqLfgLDVaIkasREnjMI7oe8oAtG2ju2aq-xSAQZ_U-7-rsyBpoy0jnwRmlyUxhXgIX0zrfBQNXEjzPtg-iJ14R5qz1iOAJL7NtQQeuYngGTj6msDlKGEd_MQTLAFDbpiVPwWX00jLT1Ll3_zhivpPCUAmC8Yz58khkqrqi4FdIxJTDkxd0PFOBH8DYicF7ls4UdOHT24mAKDwUF_TfZ32oiiKSzCD9MJB8GEXjzx7tDFok-HsdOjI6ZnSUJCOTj3wne2E6_a8Gq2_vp5CWyW12wthJbH79aa7JVfy5cx-cZmNid7oCe54KYclz1tdUgLPCQ1ajsEevbRJ_NBkTmY2wAmUpHODeocDaYt_2AwAU2DLiv2uZuaVszNSUy593Zrzxq5AaY-oWbEeD24SyEWJObJtz5knYzr4NxjZShcjx9ezwiwkRZMtLZpA_cCPFAK1nOrN8zHCOZquS17CCSLDySLvGbxNqYeBa_lGSq8cQuQo8yybd1WkuLKUjUiJecmH2XcZNTPCtdRe0eLlRtk5928AQGsQugwSig"; + + + // String listData = "X65trOhRSpeaN-2qP0zhdYi2jeJDcrTz2JHkc6UFG17xAho-VO0fkD0cA8wxoxcqyTaulOiSzaidZ2VeIvKjuinlKT2r23kdMJxjJodOZojssxgGSxm5gnry2tq5X8dbP7n-jodvAvLE9Gtq7AaBQ36ZQBQ2RcFB3TiHKHGin0gfn6T6A80orYD7i-Bdc0rh_pBEdLwGt1wWY_C8QuxeBmMWh0jmLVfpa3ZZOXVc9I7wIxzc1taQ7f-8Om9SNfXc"; + String listData = "Sf_FO8YC9EUNTeM0n9EVuDzwvLz3DcxOuG4-5UZ9486lLHAx7IOuAhPgVHpQGsQiqJ7Y3fTaWFr6rRFPL12rVg"; + + // 解密步骤 + strData = RSA.publicDecrypt(strData, RSA.getPublicKey(properties.getPublicKey())); + mapData = RSA.publicDecrypt(mapData, RSA.getPublicKey(properties.getPublicKey())); + listData = RSA.publicDecrypt(listData, RSA.getPublicKey(properties.getPublicKey())); + System.out.println(strData); + System.out.println(mapData); + System.out.println(listData); + + //其他 + assertThat(StrUtil.toMD5(JsonUtil.json2Obj(strData, String.class)) +// ).isEqualTo("fc4ead323d52f2b1122d1a9634c865c6"); +// ).isEqualTo("c4ca4238a0b923820dcc509a6f75849b"); +// ).isEqualTo("b326b5062b2f0e69046810717534cb09"); + ).isEqualTo("c977050805d8d1ebaa1e03525cbaee15"); + + //map + assertThat( + StrUtil.toMD5( + StrUtil.getSignContent( + JsonUtil.json2Obj(mapData, Map.class) + ) + ) + ).isEqualTo("f721e7b0d5415302f5fe7dc5beb2938a"); + + + //list + assertThat( + StrUtil.toMD5( + JsonUtil.obj2Json( + JsonUtil.json2Obj(listData, List.class) + ) + ) +// ).isEqualTo("2a8d8eccabadc897ad65c04940bd833b"); + ).isEqualTo("efe23825367fc9997a9667463e34753a"); + } } diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/IndexCtrl.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/IndexCtrl.java new file mode 100644 index 0000000..0e4dea5 --- /dev/null +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/IndexCtrl.java @@ -0,0 +1,91 @@ +package com.yexuejc.springboot.base.web; + +import com.yexuejc.base.http.Resps; +import com.yexuejc.base.pojo.PagerVO; +import com.yexuejc.base.util.JsonUtil; +import com.yexuejc.base.util.StrUtil; +import org.springframework.data.repository.query.Param; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author: maxf + * @date: 2018/5/12 14:17 + */ +@RestController +public class IndexCtrl { + + @RequestMapping(value = {"/", "/index"}) + public Resps index() { + return Resps.success("请求成功"); + } + + @RequestMapping(value = {"/2"}) + public Resps a(String name, @Param("name") String pwd) { + System.out.println("请求参数:name = [" + name + "], pwd = [" + pwd + "]"); + return Resps.success("请求成功").setSucc("id>" + StrUtil.genUUID()); + } + + @RequestMapping(value = {"/4"}) + public Resps e() { + List list = new ArrayList(); + list.add("asdsad"); + list.add("发生大幅度发"); + list.add("1351615"); + return Resps.success("请求成功").setSucc(list); + } + + @RequestMapping(value = {"/5"}) + public Resps f() { + List list = new ArrayList(); + PagerVO pq = new PagerVO(); + pq.setPage(333); + pq.setSize(1); + list.add(new PagerVO()); + PagerVO pw = new PagerVO(); + pw.setPage(23); + pw.setSize(255); + list.add(pw); + PagerVO p = new PagerVO(); + p.setPage(555); + list.add(p); + return Resps.success("请求成功").setSucc(list); + } + + @RequestMapping(value = {"/6"}) + public Resps h() { + return Resps.success("请求成功").setSucc(1); + } + + @RequestMapping(value = {"/7"}) + public Resps k() { + return Resps.success("请求成功").setSucc(true); + } + + @RequestMapping(value = {"/8"}) + public Resps l() { + return Resps.success("请求成功").setSucc(05652.154); + } + + @RequestMapping(value = {"/3"}) + public Resps b(@RequestBody PagerVO pagerVO) { + System.out.println("请求参数:" + JsonUtil.obj2Json(pagerVO)); + Map map = new HashMap(); + map.put("page", 5); + map.put("size", 16); + map.put("content", "定制榻榻米垫竹编客厅茶几垫卧室地毯竹地毯飘窗垫日式榻榻米地毯"); + map.put("content2", map.get("content")); + map.put("content3", map.get("content")); + map.put("content4", map.get("content")); + map.put("content5", map.get("content")); + map.put("content6", map.get("content")); + return Resps.success().setSucc(map); + } + +} diff --git a/yexuejc-springboot-base/src/test/resources/application.properties b/yexuejc-springboot-base/src/test/resources/application.properties index 3e5267b..3caa578 100644 --- a/yexuejc-springboot-base/src/test/resources/application.properties +++ b/yexuejc-springboot-base/src/test/resources/application.properties @@ -1,4 +1,18 @@ server.port=8080 logging.level.root=info -security.basic.enabled=false \ No newline at end of file +security.basic.enabled=false + +yexuejc.http.filter.type=0 + +yexuejc.http.encrypt.private-key=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAiSo5blJ9-QJ0_QElcy5AaRTq-3oO4lJ8PvIOIt-Xr5SUFODVj3DUbiy6_0bxQYO3NiYHlXPb37UVV3jjlXJsXwIDAQABAkBE0WOJH2hGs93gRl_0vwLf9ffDfkTTdlER_73p70aad3QZRslEkinQH7G5aE_DgBm5m72TCeH-PD2FZ2lwtavBAiEAvnRown5Lpqbl0tN_OUxr_e1u9d_-8dNL_JEETO7BZCECIQC4XtY-18j0bVVLxaXPjKQ00D59yntwObihDNyRK0nAfwIgHPHEGgrnpGQo-Wl7JFIg925mNqfcLxRVsAS6CpcefQECIQCUsLdsmy6QIhTmNRJSXoSXq1KatE_05DhIekzwLs8eFQIgfMawMiu52ZxBI5_pZ7ancQZ6Dsxl45utFqJShzV1pio +yexuejc.http.encrypt.public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIkqOW5SffkCdP0BJXMuQGkU6vt6DuJSfD7yDiLfl6-UlBTg1Y9w1G4suv9G8UGDtzYmB5Vz29-1FVd445VybF8CAwEAAQ +yexuejc.http.encrypt.encrypt=true +yexuejc.http.encrypt.decrypt=true + + +#编码 +spring.http.encoding.force=true +spring.http.encoding.charset=UTF-8 +spring.http.encoding.enabled=true +server.tomcat.uri-encoding=UTF-8 \ No newline at end of file