Compare commits
68 Commits
Author | SHA1 | Date |
---|---|---|
cheney | 5220f92b0a | 3 months ago |
cheney | 0ffff16f2d | 4 months ago |
cheney | 5d18cd233f | 4 months ago |
cheney | b4637b9fe1 | 6 months ago |
cheney | 11dc060ead | 6 months ago |
cheney | 42f8eb491b | 7 months ago |
cheney | 7ffa1b4e1f | 8 months ago |
cheney | a0353a8567 | 9 months ago |
cheney | 1d7e279bf7 | 9 months ago |
cheney | 1ad54fa4ca | 9 months ago |
cheney | ee5bcc0143 | 9 months ago |
cheney | 302a1d999a | 9 months ago |
cheney | f80f84a13b | 9 months ago |
cheney | b4a0a1c252 | 9 months ago |
cheney | 2ef382c180 | 9 months ago |
rong.liu | 5e78724405 | 11 months ago |
rong.liu | 9be8912160 | 11 months ago |
rong.liu | cf290c52c5 | 1 year ago |
rong.liu | c75bae7dfa | 1 year ago |
cheney | aaae4c9b6c | 1 year ago |
cheney | 4992c36aed | 1 year ago |
rong.liu | a34138190d | 1 year ago |
rong.liu | 78bee48c55 | 1 year ago |
rong.liu | 9159c0f50c | 2 years ago |
rong.liu | b8695d2281 | 2 years ago |
rong.liu | 93091ff316 | 2 years ago |
rong.liu | 5af13ce83d | 2 years ago |
rong.liu | 22cc03c9cd | 2 years ago |
rong.liu | 56e5644480 | 2 years ago |
rong.liu | 1db80e42e9 | 2 years ago |
cheney | adec7258a7 | 2 years ago |
cheney | 71bf729d36 | 2 years ago |
cheney | 26d555a99f | 2 years ago |
rong.liu | d6b620f945 | 2 years ago |
cheney | 202b9d7846 | 2 years ago |
cheney | af76be07c9 | 2 years ago |
rong.liu | cd11e047e2 | 2 years ago |
rong.liu | 4dfe29f07d | 2 years ago |
cheney | 7e6b38c828 | 2 years ago |
rong.liu | 1f9bbe96bc | 2 years ago |
rong.liu | 9a4264d707 | 2 years ago |
rong.liu | 0f85c6f0e2 | 2 years ago |
cheney | 013178aeb1 | 2 years ago |
cheney | 89019922ff | 2 years ago |
cheney | 6826d59ad7 | 2 years ago |
rong.liu | 3ebc8cdf60 | 2 years ago |
rong.liu | c16f137dc6 | 2 years ago |
cheney | 9a1c599124 | 2 years ago |
cheney | c7d5171a3d | 2 years ago |
cheney | e6b8893c28 | 2 years ago |
cheney | 81d1a277b8 | 2 years ago |
cheney | f4502cd67b | 2 years ago |
cheney | b6b94646f3 | 2 years ago |
cheney | 5f1da038eb | 2 years ago |
rong.liu | ff46f472ca | 2 years ago |
rong.liu | dabe5e677e | 2 years ago |
rong.liu | 5e48be6055 | 2 years ago |
rong.liu | 0be522886a | 2 years ago |
rong.liu | 17964852a3 | 2 years ago |
rong.liu | 73e9b218c2 | 2 years ago |
rong.liu | 181e7fd5f1 | 2 years ago |
rong.liu | 3d168c196e | 2 years ago |
rong.liu | 5121af4fb5 | 2 years ago |
rong.liu | 892d516332 | 2 years ago |
rong.liu | 9ce3ca891a | 2 years ago |
rong.liu | c80c212572 | 2 years ago |
cheney | 2ebe0ee14c | 2 years ago |
cheney | 21849bec54 | 2 years ago |
100 changed files with 3021 additions and 316 deletions
@ -0,0 +1,3 @@ |
|||||||
|
/.idea/ |
||||||
|
**/target/ |
||||||
|
*.class |
@ -0,0 +1,98 @@ |
|||||||
|
package com.huoran.iasf.common.advice; |
||||||
|
|
||||||
|
|
||||||
|
import com.huoran.iasf.common.annotation.Decrypt; |
||||||
|
import com.huoran.iasf.common.config.SecretKeyConfig; |
||||||
|
import com.huoran.iasf.common.exception.EncryptRequestException; |
||||||
|
import com.huoran.iasf.common.utils.Base64Util; |
||||||
|
import com.huoran.iasf.common.utils.JsonUtils; |
||||||
|
import com.huoran.iasf.common.utils.RSAUtil; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpInputMessage; |
||||||
|
|
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.ByteArrayInputStream; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
public class DecryptHttpInputMessage implements HttpInputMessage { |
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(this.getClass()); |
||||||
|
private HttpHeaders headers; |
||||||
|
private InputStream body; |
||||||
|
|
||||||
|
|
||||||
|
public DecryptHttpInputMessage(HttpInputMessage inputMessage, SecretKeyConfig secretKeyConfig, Decrypt decrypt) throws Exception { |
||||||
|
|
||||||
|
String privateKey = secretKeyConfig.getPrivateKey(); |
||||||
|
String charset = secretKeyConfig.getCharset(); |
||||||
|
boolean showLog = secretKeyConfig.isShowLog(); |
||||||
|
boolean timestampCheck = secretKeyConfig.isTimestampCheck(); |
||||||
|
|
||||||
|
if (StringUtils.isEmpty(privateKey)) { |
||||||
|
throw new IllegalArgumentException("privateKey is null"); |
||||||
|
} |
||||||
|
|
||||||
|
this.headers = inputMessage.getHeaders(); |
||||||
|
String content = new BufferedReader(new InputStreamReader(inputMessage.getBody())) |
||||||
|
.lines().collect(Collectors.joining(System.lineSeparator())); |
||||||
|
String decryptBody; |
||||||
|
// 未加密内容
|
||||||
|
if (content.startsWith("{")||StringUtils.isNumeric(content)||content.length()<10) { |
||||||
|
// 必须加密
|
||||||
|
if (decrypt.required()) { |
||||||
|
log.error("not support unencrypted content:{}", content); |
||||||
|
throw new EncryptRequestException("not support unencrypted content"); |
||||||
|
} |
||||||
|
log.info("Unencrypted without decryption:{}", content); |
||||||
|
decryptBody = content; |
||||||
|
} else { |
||||||
|
StringBuilder json = new StringBuilder(); |
||||||
|
content = content.replaceAll(" ", "+"); |
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(content)) { |
||||||
|
String[] contents = content.split("\\|"); |
||||||
|
for (String value : contents) { |
||||||
|
value = new String(RSAUtil.decrypt(Base64Util.decode(value), privateKey), charset); |
||||||
|
json.append(value); |
||||||
|
} |
||||||
|
} |
||||||
|
decryptBody = json.toString(); |
||||||
|
if(showLog) { |
||||||
|
log.info("Encrypted data received:{},After decryption:{}", content, decryptBody); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 开启时间戳检查
|
||||||
|
if (timestampCheck) { |
||||||
|
// 容忍最小请求时间
|
||||||
|
long toleranceTime = System.currentTimeMillis() - decrypt.timeout(); |
||||||
|
long requestTime = JsonUtils.getNode(decryptBody, "timestamp").asLong(); |
||||||
|
// 如果请求时间小于最小容忍请求时间, 判定为超时
|
||||||
|
if (requestTime < toleranceTime) { |
||||||
|
log.error("Encryption request has timed out, toleranceTime:{}, requestTime:{}, After decryption:{}", toleranceTime, requestTime, decryptBody); |
||||||
|
throw new EncryptRequestException("request timeout"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.body = new ByteArrayInputStream(decryptBody.getBytes()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public InputStream getBody(){ |
||||||
|
return body; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public HttpHeaders getHeaders() { |
||||||
|
return headers; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
package com.huoran.iasf.common.advice; |
||||||
|
|
||||||
|
|
||||||
|
import com.huoran.iasf.common.annotation.Decrypt; |
||||||
|
import com.huoran.iasf.common.config.SecretKeyConfig; |
||||||
|
import com.huoran.iasf.common.exception.EncryptRequestException; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
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.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
@ControllerAdvice |
||||||
|
public class EncryptRequestBodyAdvice implements RequestBodyAdvice { |
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(this.getClass()); |
||||||
|
|
||||||
|
private boolean encrypt; |
||||||
|
private Decrypt decryptAnnotation; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private SecretKeyConfig secretKeyConfig; |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||||
|
Method method = methodParameter.getMethod(); |
||||||
|
if (Objects.isNull(method)) { |
||||||
|
encrypt = false; |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (method.isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) { |
||||||
|
encrypt = true; |
||||||
|
decryptAnnotation = methodParameter.getMethodAnnotation(Decrypt.class); |
||||||
|
return true; |
||||||
|
} |
||||||
|
// 此处如果按照原逻辑直接返回encrypt, 会造成一次修改为true之后, 后续请求都会变成true, 在不支持时, 需要做修正
|
||||||
|
encrypt = false; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||||
|
return body; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, |
||||||
|
Class<? extends HttpMessageConverter<?>> converterType){ |
||||||
|
if (encrypt) { |
||||||
|
try { |
||||||
|
return new DecryptHttpInputMessage(inputMessage, secretKeyConfig, decryptAnnotation); |
||||||
|
} catch (EncryptRequestException e) { |
||||||
|
throw e; |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("Decryption failed", e); |
||||||
|
} |
||||||
|
} |
||||||
|
return inputMessage; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, |
||||||
|
Class<? extends HttpMessageConverter<?>> converterType) { |
||||||
|
return body; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package com.huoran.iasf.common.advice; |
||||||
|
|
||||||
|
|
||||||
|
import com.huoran.iasf.common.annotation.Encrypt; |
||||||
|
import com.huoran.iasf.common.config.SecretKeyConfig; |
||||||
|
import com.huoran.iasf.common.utils.Base64Util; |
||||||
|
import com.huoran.iasf.common.utils.JsonUtils; |
||||||
|
import com.huoran.iasf.common.utils.RSAUtil; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.http.server.ServerHttpRequest; |
||||||
|
import org.springframework.http.server.ServerHttpResponse; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
@ControllerAdvice |
||||||
|
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> { |
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(this.getClass()); |
||||||
|
|
||||||
|
private boolean encrypt; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private SecretKeyConfig secretKeyConfig; |
||||||
|
|
||||||
|
private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||||
|
Method method = returnType.getMethod(); |
||||||
|
if (Objects.isNull(method)) { |
||||||
|
return encrypt; |
||||||
|
} |
||||||
|
encrypt = method.isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen(); |
||||||
|
return encrypt; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, |
||||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { |
||||||
|
// EncryptResponseBodyAdvice.setEncryptStatus(false);
|
||||||
|
// Dynamic Settings Not Encrypted
|
||||||
|
Boolean status = encryptLocal.get(); |
||||||
|
if (null != status && !status) { |
||||||
|
encryptLocal.remove(); |
||||||
|
return body; |
||||||
|
} |
||||||
|
if (encrypt) { |
||||||
|
String publicKey = secretKeyConfig.getPublicKey(); |
||||||
|
try { |
||||||
|
String content = JsonUtils.writeValueAsString(body); |
||||||
|
if (!StringUtils.hasText(publicKey)) { |
||||||
|
throw new NullPointerException("Please configure rsa.encrypt.privatekeyc parameter!"); |
||||||
|
} |
||||||
|
byte[] data = content.getBytes(); |
||||||
|
byte[] encodedData = RSAUtil.encrypt(data, publicKey); |
||||||
|
String result = Base64Util.encode(encodedData); |
||||||
|
if(secretKeyConfig.isShowLog()) { |
||||||
|
log.info("Pre-encrypted data:{},After encryption:{}", content, result); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("Encrypted data exception", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return body; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package com.huoran.iasf.common.annotation; |
||||||
|
|
||||||
|
|
||||||
|
import com.huoran.iasf.common.exception.EncryptRequestException; |
||||||
|
|
||||||
|
import java.lang.annotation.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 16:45 |
||||||
|
**/ |
||||||
|
@Target(ElementType.METHOD) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Documented |
||||||
|
public @interface Decrypt{ |
||||||
|
|
||||||
|
/** |
||||||
|
* 请求参数一定要是加密内容 |
||||||
|
*/ |
||||||
|
boolean required() default true; |
||||||
|
|
||||||
|
/** |
||||||
|
* 请求数据时间戳校验时间差 |
||||||
|
* 超过(当前时间-指定时间)的数据认定为伪造 |
||||||
|
* 注意应用程序需要捕获 {@link EncryptRequestException} 异常 |
||||||
|
*/ |
||||||
|
long timeout() default 3000; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package com.huoran.iasf.common.annotation; |
||||||
|
|
||||||
|
|
||||||
|
import com.huoran.iasf.common.advice.EncryptRequestBodyAdvice; |
||||||
|
import com.huoran.iasf.common.advice.EncryptResponseBodyAdvice; |
||||||
|
import com.huoran.iasf.common.config.SecretKeyConfig; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
|
||||||
|
import java.lang.annotation.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 16:44 |
||||||
|
**/ |
||||||
|
@Target({ElementType.TYPE}) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Inherited |
||||||
|
@Documented |
||||||
|
@Import({SecretKeyConfig.class, |
||||||
|
EncryptResponseBodyAdvice.class, |
||||||
|
EncryptRequestBodyAdvice.class}) |
||||||
|
public @interface EnableSecurity{ |
||||||
|
|
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package com.huoran.iasf.common.annotation; |
||||||
|
|
||||||
|
import java.lang.annotation.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 16:45 |
||||||
|
**/ |
||||||
|
@Target(ElementType.METHOD) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Documented |
||||||
|
public @interface Encrypt{ |
||||||
|
|
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
package com.huoran.iasf.common.config; |
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
|
||||||
|
/** |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
@ConfigurationProperties(prefix = "rsa.encrypt") |
||||||
|
@Configuration |
||||||
|
public class SecretKeyConfig { |
||||||
|
|
||||||
|
private String privateKey; |
||||||
|
|
||||||
|
private String publicKey; |
||||||
|
|
||||||
|
private String charset = "UTF-8"; |
||||||
|
|
||||||
|
private boolean open = true; |
||||||
|
|
||||||
|
private boolean showLog = false; |
||||||
|
|
||||||
|
/** |
||||||
|
* 请求数据时间戳校验时间差 |
||||||
|
* 超过指定时间的数据认定为伪造 |
||||||
|
*/ |
||||||
|
private boolean timestampCheck = false; |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
public String getCharset() { |
||||||
|
return charset; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCharset(String charset) { |
||||||
|
this.charset = charset; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isOpen() { |
||||||
|
return open; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOpen(boolean open) { |
||||||
|
this.open = open; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isShowLog() { |
||||||
|
return showLog; |
||||||
|
} |
||||||
|
|
||||||
|
public void setShowLog(boolean showLog) { |
||||||
|
this.showLog = showLog; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isTimestampCheck() { |
||||||
|
return timestampCheck; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTimestampCheck(boolean timestampCheck) { |
||||||
|
this.timestampCheck = timestampCheck; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package com.huoran.iasf.common.exception; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author imyzt |
||||||
|
* @date 2020/06/02 |
||||||
|
* @description 加密请求超时异常 |
||||||
|
*/ |
||||||
|
public class EncryptRequestException extends RuntimeException { |
||||||
|
|
||||||
|
public EncryptRequestException(String msg) { |
||||||
|
super(msg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package com.huoran.iasf.common.exception; |
||||||
|
|
||||||
|
import com.huoran.iasf.common.exception.code.BaseResponseCode; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
/** |
||||||
|
* BusinessException |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @version V1.0 |
||||||
|
* @date 2022年7月28日 |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
public class NotFoundException extends RuntimeException { |
||||||
|
private BaseResponseCode baseResponseCode; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.huoran.iasf.common.exception; |
||||||
|
|
||||||
|
import com.huoran.iasf.common.exception.code.BaseResponseCode; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
@Getter |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
public class UnauthorizedException extends RuntimeException { |
||||||
|
private BaseResponseCode baseResponseCode; |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package com.huoran.iasf.common.filter; |
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils; |
||||||
|
import org.springframework.core.annotation.Order; |
||||||
|
|
||||||
|
import javax.servlet.*; |
||||||
|
import javax.servlet.annotation.WebFilter; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true) |
||||||
|
@Order(2) |
||||||
|
public class XSSFilter implements Filter { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void init(FilterConfig filterConfig1) throws ServletException { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) |
||||||
|
throws IOException, ServletException { |
||||||
|
|
||||||
|
//注入xss过滤器实例
|
||||||
|
if(arg2 != null){ |
||||||
|
HttpServletRequest req = (HttpServletRequest) arg0; |
||||||
|
String method = req.getMethod(); |
||||||
|
boolean methodB = false; |
||||||
|
if("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)){ |
||||||
|
methodB = true; |
||||||
|
} |
||||||
|
|
||||||
|
if (methodB && req.getContentType() != null && req.getContentType().startsWith("multipart/")) { |
||||||
|
// 过滤
|
||||||
|
arg2.doFilter(req, arg1); |
||||||
|
}else { |
||||||
|
HttpServletResponse response = (HttpServletResponse) arg1; |
||||||
|
XssHttpServletRequestWrapper reqW = new XssHttpServletRequestWrapper(req); |
||||||
|
//过滤
|
||||||
|
arg2.doFilter(reqW, response); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void destroy() { |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,265 @@ |
|||||||
|
package com.huoran.iasf.common.filter; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject; |
||||||
|
import com.huoran.iasf.common.exception.BusinessException; |
||||||
|
import com.huoran.iasf.common.exception.code.BaseResponseCode; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.jsoup.Jsoup; |
||||||
|
import org.jsoup.nodes.Document; |
||||||
|
import org.jsoup.safety.Safelist; |
||||||
|
|
||||||
|
import javax.servlet.ReadListener; |
||||||
|
import javax.servlet.ServletInputStream; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletRequestWrapper; |
||||||
|
import java.io.*; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { |
||||||
|
|
||||||
|
/** |
||||||
|
* post请求体 |
||||||
|
*/ |
||||||
|
private byte[] body; |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否是文件上传 |
||||||
|
*/ |
||||||
|
private boolean fileUpload = true; |
||||||
|
|
||||||
|
//富文本验证链接
|
||||||
|
private static final String[] whiteList = |
||||||
|
{"/iasf/sysContent/save", |
||||||
|
"/iasf/sysContent/update"}; |
||||||
|
|
||||||
|
/** |
||||||
|
* sql注入正则 |
||||||
|
*/ |
||||||
|
private static String badStrReg = |
||||||
|
"\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; |
||||||
|
|
||||||
|
/** |
||||||
|
* xss脚本正则 |
||||||
|
*/ |
||||||
|
private final static Pattern[] scriptPatterns = { |
||||||
|
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), |
||||||
|
Pattern.compile("</script>", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), |
||||||
|
Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), |
||||||
|
Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), |
||||||
|
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("script", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("ScriPT", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("meta", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE), |
||||||
|
Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) |
||||||
|
}; |
||||||
|
|
||||||
|
public XssHttpServletRequestWrapper() { |
||||||
|
super(null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构造函数 - 获取post请求体 |
||||||
|
* @param httpservletrequest |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public XssHttpServletRequestWrapper(HttpServletRequest httpservletrequest) throws IOException { |
||||||
|
super(httpservletrequest); |
||||||
|
String sessionStream = getBodyString(httpservletrequest); |
||||||
|
body = sessionStream.getBytes(StandardCharsets.UTF_8); |
||||||
|
System.out.println(httpservletrequest.getRequestURI()); |
||||||
|
if(Arrays.asList(whiteList).contains(httpservletrequest.getRequestURI())){ |
||||||
|
fileUpload = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 读取post请求体 |
||||||
|
* @param httpservletrequest |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
private String getBodyString(HttpServletRequest httpservletrequest) throws IOException { |
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
InputStream ins = httpservletrequest.getInputStream(); |
||||||
|
try (BufferedReader isr = new BufferedReader(new InputStreamReader(ins, StandardCharsets.UTF_8));) { |
||||||
|
String line = ""; |
||||||
|
while ((line = isr.readLine()) != null) { |
||||||
|
sb.append(line); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
throw e; |
||||||
|
} |
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤springmvc中的 @RequestParam 注解中的参数 |
||||||
|
* @param s |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String[] getParameterValues(String s) { |
||||||
|
String[] str = super.getParameterValues(s); |
||||||
|
if (str == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
int i = str.length; |
||||||
|
String[] as1 = new String[i]; |
||||||
|
for (int j = 0; j < i; j++) { |
||||||
|
as1[j] = cleanXSS(cleanSQLInject(str[j])); |
||||||
|
} |
||||||
|
// log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", Arrays.toString(as1));
|
||||||
|
return as1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤request.getParameter的参数 |
||||||
|
* @param s |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getParameter(String s) { |
||||||
|
String s1 = super.getParameter(s); |
||||||
|
if (s1 == null) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
String s2 = cleanXSS(cleanSQLInject(s1)); |
||||||
|
// log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", s2);
|
||||||
|
return s2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤请求体 json 格式的 |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public ServletInputStream getInputStream() throws IOException { |
||||||
|
// 非文件上传进行过滤
|
||||||
|
if (!fileUpload) { |
||||||
|
|
||||||
|
try { |
||||||
|
// 解析请求体为字符串
|
||||||
|
String bodyStr = new String(body, StandardCharsets.UTF_8); |
||||||
|
|
||||||
|
// 清理HTML,只允许安全的元素和属性
|
||||||
|
Safelist safelist = Safelist.basicWithImages(); // 自定义safelist
|
||||||
|
String safeHtml = Jsoup.clean(bodyStr, "", safelist, new Document.OutputSettings().prettyPrint(false)); |
||||||
|
cleanSQLInject(safeHtml); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
// 处理解析或处理过程中的任何异常
|
||||||
|
log.error("Error processing request body {}", e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
// 将请求体参数流转 -- 流读取一次就会消失,所以我们事先读取之后就存在byte数组里边方便流转
|
||||||
|
final ByteArrayInputStream bais = new ByteArrayInputStream(body); |
||||||
|
return new ServletInputStream() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public int read() throws IOException { |
||||||
|
return bais.read(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isFinished() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isReady() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setReadListener(ReadListener readListener) { |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public String cleanXSS(String src) { |
||||||
|
if (StringUtils.isBlank(src)) { |
||||||
|
return src; |
||||||
|
} |
||||||
|
|
||||||
|
// 创建一个允许的HTML标签和属性的Safelist
|
||||||
|
Safelist safelist = Safelist.relaxed() // 允许大多数基本的HTML标签和属性
|
||||||
|
.addTags("img") // 添加额外的标签,如img(记得添加允许的属性,如src和alt)
|
||||||
|
.addAttributes("*", "class") // 允许所有标签使用"class"属性
|
||||||
|
.addAttributes("img", "src", "alt") // 允许img标签的src和alt属性
|
||||||
|
.addProtocols("img", "src", "http", "https") // 只允许http和https协议的src
|
||||||
|
; // 移除协议相对URL,避免安全问题
|
||||||
|
|
||||||
|
// 使用JSoup进行清理
|
||||||
|
Document document = Jsoup.parseBodyFragment(src, ""); // 解析HTML片段
|
||||||
|
document.outputSettings(new Document.OutputSettings().prettyPrint(false)); // 禁止美化输出,保持原始结构
|
||||||
|
String html = document.html(); |
||||||
|
String clean = Jsoup.clean(html, "", safelist);// 使用Safelist进行清理
|
||||||
|
|
||||||
|
return clean; // 返回清理后的HTML字符串
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清除xss |
||||||
|
* @param src 单个参数 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
/*public String cleanXSS(String src) { |
||||||
|
if(StringUtils.isBlank(src)){ |
||||||
|
return src; |
||||||
|
} |
||||||
|
String temp = src; |
||||||
|
// 校验xss脚本
|
||||||
|
for (Pattern pattern : scriptPatterns) { |
||||||
|
temp = pattern.matcher(temp).replaceAll(""); |
||||||
|
} |
||||||
|
// 校验xss特殊字符 匹配一个空白字符(包括空格、制表符、换页符和换行符等)//这个可以不用,因为有写数据用富文本编辑的时候有换行
|
||||||
|
// temp = temp.replaceAll("\0|\n|\r", "");
|
||||||
|
temp = temp.replaceAll("<", "<").replaceAll(">", ">"); |
||||||
|
|
||||||
|
if (!temp.equals(src)) { |
||||||
|
|
||||||
|
log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!"); |
||||||
|
log.error("原始输入信息-->" + temp); |
||||||
|
|
||||||
|
throw new BusinessException(BaseResponseCode.XSS_FILTER); |
||||||
|
} |
||||||
|
|
||||||
|
return src; |
||||||
|
}*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤sql注入 -- 需要增加通配,过滤大小写组合 |
||||||
|
* @param src 单个参数值 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public String cleanSQLInject(String src) { |
||||||
|
if(StringUtils.isBlank(src)){ |
||||||
|
return src; |
||||||
|
} |
||||||
|
String cleanedText = Jsoup.clean(src, Safelist.basic()); |
||||||
|
|
||||||
|
String SQL_KEYWORD_PATTERN = |
||||||
|
"(?i)(?:(?!<[^>]*?>))((select|update|insert|delete|drop|create|alter|exec|union|table|database)[^a-zA-Z0-9])"; |
||||||
|
|
||||||
|
// 过滤SQL关键字
|
||||||
|
cleanedText = cleanedText.replaceAll(SQL_KEYWORD_PATTERN, ""); |
||||||
|
// 非法sql注入正则
|
||||||
|
// Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);
|
||||||
|
// if (sqlPattern.matcher(src.toLowerCase()).find()) {
|
||||||
|
// log.error("sql注入检查:输入信息存在SQL攻击!");
|
||||||
|
// throw new BusinessException(BaseResponseCode.SQL_FILTER);
|
||||||
|
// }
|
||||||
|
return cleanedText; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package com.huoran.iasf.common.utils; |
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base64 |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
public class Base64Util{ |
||||||
|
|
||||||
|
/** |
||||||
|
* Decoding to binary |
||||||
|
* @param base64 base64 |
||||||
|
* @return byte |
||||||
|
* @throws Exception Exception |
||||||
|
*/ |
||||||
|
public static byte[] decode(String base64) throws Exception { |
||||||
|
return Base64.decodeBase64(base64); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Binary encoding as a string |
||||||
|
* @param bytes byte |
||||||
|
* @return String |
||||||
|
* @throws Exception Exception |
||||||
|
*/ |
||||||
|
public static String encode(byte[] bytes) throws Exception { |
||||||
|
return new String(Base64.encodeBase64(bytes)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package com.huoran.iasf.common.utils; |
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException; |
||||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author imyzt |
||||||
|
* @date 2020/06/08 |
||||||
|
* @description JSON 工具类 |
||||||
|
*/ |
||||||
|
public class JsonUtils { |
||||||
|
|
||||||
|
private JsonUtils() { |
||||||
|
} |
||||||
|
|
||||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
||||||
|
|
||||||
|
public static JsonNode getNode(String content, String key) throws IOException { |
||||||
|
JsonNode jsonNode = OBJECT_MAPPER.readTree(content); |
||||||
|
return jsonNode.get(key); |
||||||
|
} |
||||||
|
|
||||||
|
public static String writeValueAsString(Object body) throws JsonProcessingException { |
||||||
|
return OBJECT_MAPPER.writeValueAsString(body); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
package com.huoran.iasf.common.utils; |
||||||
|
|
||||||
|
import javax.crypto.Cipher; |
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.security.Key; |
||||||
|
import java.security.KeyFactory; |
||||||
|
import java.security.spec.PKCS8EncodedKeySpec; |
||||||
|
import java.security.spec.X509EncodedKeySpec; |
||||||
|
|
||||||
|
/** |
||||||
|
* RSA Util |
||||||
|
* Author:Bobby |
||||||
|
* DateTime:2019/4/9 |
||||||
|
**/ |
||||||
|
public class RSAUtil{ |
||||||
|
|
||||||
|
/** |
||||||
|
* encryption algorithm RSA |
||||||
|
*/ |
||||||
|
public static final String KEY_ALGORITHM = "RSA"; |
||||||
|
|
||||||
|
/** |
||||||
|
* RSA Maximum Encrypted Plaintext Size |
||||||
|
*/ |
||||||
|
private static final int MAX_ENCRYPT_BLOCK = 117; |
||||||
|
|
||||||
|
/** |
||||||
|
* RSA Maximum decrypted ciphertext size |
||||||
|
*/ |
||||||
|
private static final int MAX_DECRYPT_BLOCK = 256; |
||||||
|
|
||||||
|
/** |
||||||
|
* encryption |
||||||
|
* @param data data |
||||||
|
* @param publicKey publicKey |
||||||
|
* @return byte |
||||||
|
* @throws Exception Exception |
||||||
|
*/ |
||||||
|
public static byte[] encrypt(byte[] data, String publicKey) |
||||||
|
throws Exception { |
||||||
|
byte[] keyBytes = Base64Util.decode(publicKey); |
||||||
|
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); |
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
||||||
|
Key publicK = keyFactory.generatePublic(x509KeySpec); |
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); |
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicK); |
||||||
|
int inputLen = data.length; |
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||||
|
int offSet = 0; |
||||||
|
byte[] cache; |
||||||
|
int i = 0; |
||||||
|
// Sectional Encryption of Data
|
||||||
|
while (inputLen - offSet > 0) { |
||||||
|
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { |
||||||
|
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); |
||||||
|
} else { |
||||||
|
cache = cipher.doFinal(data, offSet, inputLen - offSet); |
||||||
|
} |
||||||
|
out.write(cache, 0, cache.length); |
||||||
|
i++; |
||||||
|
offSet = i * MAX_ENCRYPT_BLOCK; |
||||||
|
} |
||||||
|
byte[] encryptedData = out.toByteArray(); |
||||||
|
out.close(); |
||||||
|
return encryptedData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Decrypt |
||||||
|
* @param text text |
||||||
|
* @param privateKey privateKey |
||||||
|
* @return byte |
||||||
|
* @throws Exception Exception |
||||||
|
*/ |
||||||
|
public static byte[] decrypt(byte[] text, String privateKey) |
||||||
|
throws Exception { |
||||||
|
byte[] keyBytes = Base64Util.decode(privateKey); |
||||||
|
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); |
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
||||||
|
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); |
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); |
||||||
|
cipher.init(Cipher.DECRYPT_MODE, privateK); |
||||||
|
int inputLen = text.length; |
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||||
|
int offSet = 0; |
||||||
|
byte[] cache; |
||||||
|
int i = 0; |
||||||
|
// Sectional Encryption of Data
|
||||||
|
while (inputLen - offSet > 0) { |
||||||
|
if (inputLen - offSet > MAX_DECRYPT_BLOCK) { |
||||||
|
cache = cipher.doFinal(text, offSet, MAX_DECRYPT_BLOCK); |
||||||
|
} else { |
||||||
|
cache = cipher.doFinal(text, offSet, inputLen - offSet); |
||||||
|
} |
||||||
|
out.write(cache, 0, cache.length); |
||||||
|
i++; |
||||||
|
offSet = i * MAX_DECRYPT_BLOCK; |
||||||
|
} |
||||||
|
byte[] decryptedData = out.toByteArray(); |
||||||
|
out.close(); |
||||||
|
return decryptedData; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
package com.huoran.iasf.common.utils; |
||||||
|
|
||||||
|
import com.huoran.iasf.common.exception.BusinessException; |
||||||
|
import org.springframework.web.multipart.MultipartFile; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.HashMap; |
||||||
|
|
||||||
|
import static com.huoran.iasf.common.exception.code.BaseResponseCode.UPLOAD_FAILED; |
||||||
|
|
||||||
|
/** |
||||||
|
* 文件上传校验工具类 |
||||||
|
**/ |
||||||
|
public class fileUploadUtils { |
||||||
|
|
||||||
|
|
||||||
|
// 缓存文件魔数值
|
||||||
|
public static final HashMap<String, String> mFileTypes = new HashMap<String, String>(); |
||||||
|
|
||||||
|
static { |
||||||
|
mFileTypes.put("FFD8FFE0", "jpg"); |
||||||
|
mFileTypes.put("89504E47", "png"); |
||||||
|
mFileTypes.put("47494638", "gif"); |
||||||
|
// mFileTypes.put("49492A00", "tif");
|
||||||
|
// mFileTypes.put("424D", "bmp");
|
||||||
|
// mFileTypes.put("38425053", "psd");
|
||||||
|
// mFileTypes.put("3C3F786D6C", "xml");
|
||||||
|
// mFileTypes.put("68746D6C3E", "html");
|
||||||
|
mFileTypes.put("D0CF11E0", "doc"); |
||||||
|
mFileTypes.put("D0CF11E0", "xls");//excel2003版本文件
|
||||||
|
mFileTypes.put("6D6F6F76", "mov"); |
||||||
|
mFileTypes.put("504B0304", "xlsx");//excel2007以上版本文件
|
||||||
|
// mFileTypes.put("5374616E64617264204A", "mdb");
|
||||||
|
mFileTypes.put("255044462D312E", "pdf"); |
||||||
|
mFileTypes.put("504B0304", "docx"); |
||||||
|
mFileTypes.put("00000020667479706D70","MP4"); |
||||||
|
mFileTypes.put("49443303000000002176","MP3"); |
||||||
|
// mFileTypes.put("52617221", "rar");
|
||||||
|
// mFileTypes.put("41564920", "avi");
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 文件上传校验 |
||||||
|
* |
||||||
|
* @param file 上传的文件 |
||||||
|
* @param allowedExtension 允许上传的文件后缀集合 |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public static final void assertAllowed(MultipartFile file, String[] allowedExtension) throws Exception { |
||||||
|
|
||||||
|
//通过文件魔数获取文件的原始类型
|
||||||
|
String fileExtension = mFileTypes.get(getFileHeader(file)); |
||||||
|
//原始类型与允许类型集合进行比较,判断文件是否合法
|
||||||
|
if (!isAllowedExtension(fileExtension, allowedExtension)) { |
||||||
|
throw new BusinessException(UPLOAD_FAILED); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取文件魔数值 |
||||||
|
* |
||||||
|
* @param file |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static String getFileHeader(MultipartFile file) { |
||||||
|
InputStream is = null; |
||||||
|
String value = null; |
||||||
|
try { |
||||||
|
is = file.getInputStream(); |
||||||
|
byte[] b = new byte[4]; |
||||||
|
is.read(b, 0, b.length); |
||||||
|
value = bytesToHexString(b); |
||||||
|
} catch (Exception e) { |
||||||
|
} finally { |
||||||
|
if (null != is) { |
||||||
|
try { |
||||||
|
is.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
private static String bytesToHexString(byte[] src) { |
||||||
|
StringBuilder builder = new StringBuilder(); |
||||||
|
if (src == null || src.length <= 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
String hv; |
||||||
|
for (int i = 0; i < src.length; i++) { |
||||||
|
hv = Integer.toHexString(src[i] & 0xFF).toUpperCase(); |
||||||
|
if (hv.length() < 2) { |
||||||
|
builder.append(0); |
||||||
|
} |
||||||
|
builder.append(hv); |
||||||
|
} |
||||||
|
System.out.println("文件魔数值为:" + builder.toString()); |
||||||
|
return builder.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 判断MIME类型是否是允许的MIME类型 |
||||||
|
* |
||||||
|
* @param extension |
||||||
|
* @param allowedExtension |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { |
||||||
|
for (String str : allowedExtension) { |
||||||
|
if (str.equalsIgnoreCase(extension)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
package com.huoran.iasf.controller; |
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil; |
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage; |
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||||
|
import com.huoran.iasf.common.aop.annotation.LogAnnotation; |
||||||
|
import com.huoran.iasf.common.utils.R; |
||||||
|
import com.huoran.iasf.entity.Seo; |
||||||
|
import com.huoran.iasf.entity.Site; |
||||||
|
import com.huoran.iasf.service.SeoService; |
||||||
|
import com.huoran.iasf.service.SiteService; |
||||||
|
import io.swagger.annotations.Api; |
||||||
|
import io.swagger.annotations.ApiOperation; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.bind.annotation.*; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* 前端控制器 |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @since 2023-08-24 |
||||||
|
*/ |
||||||
|
@Api(tags = "seo管理") |
||||||
|
@RestController |
||||||
|
@RequestMapping("/seo") |
||||||
|
public class SeoController { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private SeoService seoService; |
||||||
|
|
||||||
|
@PostMapping("/add") |
||||||
|
@ApiOperation(value = "新增seo") |
||||||
|
public R addUserGroup(@RequestBody Seo seo) { |
||||||
|
Seo one = seoService.getOne(new QueryWrapper<Seo>(). |
||||||
|
eq("title", seo.getTitle())); |
||||||
|
if (ObjectUtil.isNotNull(one)){ |
||||||
|
R.fail("seo已存在"); |
||||||
|
} |
||||||
|
boolean save = seoService.save(seo); |
||||||
|
return save ? R.success() : R.fail("添加失败"); |
||||||
|
} |
||||||
|
|
||||||
|
@PostMapping("/delete") |
||||||
|
@ApiOperation(value = "删除seo") |
||||||
|
public R deleted(@RequestParam Integer id) { |
||||||
|
boolean remove = seoService.removeById(id); |
||||||
|
return remove ? R.success() : R.fail("删除失败"); |
||||||
|
} |
||||||
|
|
||||||
|
@PostMapping("/update") |
||||||
|
@ApiOperation(value = "更新seo") |
||||||
|
public R update(@RequestBody Seo seo) { |
||||||
|
boolean update = seoService.updateById(seo); |
||||||
|
return update ? R.success() : R.fail("更新失败"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/list") |
||||||
|
@ApiOperation(value = "站点seo列表") |
||||||
|
public R list(@RequestParam Integer siteId) { |
||||||
|
QueryWrapper<Seo> queryWrapper = new QueryWrapper<>(); |
||||||
|
queryWrapper.eq("site_id",siteId); |
||||||
|
List<Seo> list = seoService.list(queryWrapper); |
||||||
|
return R.success(list); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,44 @@ |
|||||||
|
package com.huoran.iasf.entity; |
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||||
|
import java.io.Serializable; |
||||||
|
import io.swagger.annotations.ApiModel; |
||||||
|
import io.swagger.annotations.ApiModelProperty; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.EqualsAndHashCode; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @since 2023-08-24 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
@EqualsAndHashCode(callSuper = false) |
||||||
|
@TableName("sys_seo") |
||||||
|
@ApiModel(value="Seo对象", description="") |
||||||
|
public class Seo implements Serializable { |
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "主键") |
||||||
|
@TableId(value = "id", type = IdType.AUTO) |
||||||
|
private Integer id; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "标题") |
||||||
|
private String title; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "关键词") |
||||||
|
private String keyword; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "描述") |
||||||
|
private String description; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "站点") |
||||||
|
private Integer siteId; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.huoran.iasf.mapper; |
||||||
|
|
||||||
|
import com.huoran.iasf.entity.Seo; |
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Mapper 接口 |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @since 2023-08-24 |
||||||
|
*/ |
||||||
|
public interface SeoMapper extends BaseMapper<Seo> { |
||||||
|
|
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
|
<mapper namespace="com.huoran.iasf.mapper.SeoMapper"> |
||||||
|
|
||||||
|
</mapper> |
@ -0,0 +1,48 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
|
<mapper namespace="com.huoran.iasf.mapper.SysFilesMapper"> |
||||||
|
|
||||||
|
<select id="fileList" resultType="com.huoran.iasf.entity.SysFilesEntity" |
||||||
|
parameterType="com.huoran.iasf.entity.SysFilesEntity"> |
||||||
|
SELECT |
||||||
|
f.id, |
||||||
|
f.url, |
||||||
|
f.file_name, |
||||||
|
f.format, |
||||||
|
f.file_size, |
||||||
|
f.type, |
||||||
|
f.deleted, |
||||||
|
f.quote, |
||||||
|
f.site, |
||||||
|
f.is_release, |
||||||
|
f.is_del, |
||||||
|
f.quote_id, |
||||||
|
f.quote_type, |
||||||
|
f.create_date, |
||||||
|
u.real_name AS uploader |
||||||
|
FROM |
||||||
|
sys_files f |
||||||
|
LEFT JOIN sys_user u ON f.uploader = u.id |
||||||
|
WHERE f.deleted = 1 |
||||||
|
AND f.is_release = 1 |
||||||
|
AND f.is_del = 0 |
||||||
|
and f.quote is not null |
||||||
|
<if test="req.site != '' and req.site != null"> |
||||||
|
AND f.site = #{req.site} |
||||||
|
</if> |
||||||
|
<if test="req.type != '' and req.type != null"> |
||||||
|
AND f.type = #{req.type} |
||||||
|
</if> |
||||||
|
<if test="req.fileName != '' and req.fileName != null"> |
||||||
|
AND f.file_name LIKE '%' #{req.fileName} '%' |
||||||
|
</if> |
||||||
|
<if test="req.uploader != '' and req.uploader != null"> |
||||||
|
AND u.real_name LIKE '%' #{req.uploader} '%' |
||||||
|
</if> |
||||||
|
<if test="req.quote != '' and req.quote != null"> |
||||||
|
AND f.quote LIKE '%' #{req.quote} '%' |
||||||
|
</if> |
||||||
|
ORDER BY |
||||||
|
f.create_date DESC |
||||||
|
</select> |
||||||
|
</mapper> |
@ -0,0 +1,16 @@ |
|||||||
|
package com.huoran.iasf.service; |
||||||
|
|
||||||
|
import com.huoran.iasf.entity.Seo; |
||||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* 服务类 |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @since 2023-08-24 |
||||||
|
*/ |
||||||
|
public interface SeoService extends IService<Seo> { |
||||||
|
|
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package com.huoran.iasf.service.impl; |
||||||
|
|
||||||
|
import com.huoran.iasf.entity.Seo; |
||||||
|
import com.huoran.iasf.mapper.SeoMapper; |
||||||
|
import com.huoran.iasf.service.SeoService; |
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* 服务实现类 |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author cheney |
||||||
|
* @since 2023-08-24 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class SeoServiceImpl extends ServiceImpl<SeoMapper, Seo> implements SeoService { |
||||||
|
|
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||||
|
@Data |
||||||
|
public class AllTheQuery { |
||||||
|
|
||||||
|
@ApiParam(name = "siteId", value = "站点id", required = true) |
||||||
|
Integer siteId; |
||||||
|
@ApiParam(name = "templateId", value = "模板id", required = true) |
||||||
|
Integer templateId; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class ArticleEnableOrDisable { |
||||||
|
@ApiParam(name = "id", value = "文章id", required = true) |
||||||
|
String id; |
||||||
|
@ApiParam(name = "isDisable", value = "是否禁用(0默认,0启用 1禁用)", required = true) |
||||||
|
String isDisable; |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.EqualsAndHashCode; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class ArticleModifiedSortReq { |
||||||
|
@ApiModelProperty(value = "顺序(排序号)") |
||||||
|
private Integer sequence; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "文章id") |
||||||
|
private Integer articleId; |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class ArticleTopOperation { |
||||||
|
@ApiParam(name = "isTop", value = "是否置顶(默认为0 不置顶 1为置顶)", required = true) |
||||||
|
Integer isTop; |
||||||
|
@ApiParam(name = "articleId", value = "文章Id", required = true) |
||||||
|
Integer articleId; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||||
|
@Data |
||||||
|
public class CheckForHeavy { |
||||||
|
|
||||||
|
@ApiParam(name = "siteId", value = "站点id", required = true) |
||||||
|
Integer siteId; |
||||||
|
@ApiParam(name = "classificationName", value = "分类名称", required = true) |
||||||
|
String classificationName; |
||||||
|
@ApiParam(name = "classificationId", value = "分类id(新增不传,编辑传)", required = false) |
||||||
|
Integer classificationId; |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class LabelCheckForHeavy { |
||||||
|
|
||||||
|
@ApiParam(name = "siteId", value = "站点id", required = true) |
||||||
|
Integer siteId; |
||||||
|
@ApiParam(name = "labelName", value = "标签名称", required = true) |
||||||
|
String labelName; |
||||||
|
@ApiParam(name = "labelId", value = "标签id(新增不传,编辑传)", required = false) |
||||||
|
Integer labelId; |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class OneLevelChecksThemAll { |
||||||
|
|
||||||
|
@ApiParam(name = "id", value = "栏目id", required = true) |
||||||
|
Integer id; |
||||||
|
|
||||||
|
@ApiParam(name = "isSort", value = "判断是否为排序接口调用(1为排序接口调用 0我栏目管理列表调用)", required = true) |
||||||
|
Integer isSort; |
||||||
|
|
||||||
|
@ApiParam(name = "ids", value = "主键", required = true) |
||||||
|
Integer siteId; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.huoran.iasf.vo.req; |
||||||
|
|
||||||
|
import io.swagger.annotations.ApiParam; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
@Data |
||||||
|
public class SubLevelColumnsUnderALevel { |
||||||
|
|
||||||
|
@ApiParam(name = "id", value = "id", required = true) |
||||||
|
Integer id; |
||||||
|
@ApiParam(name = "siteId", value = "站点id", required = true) |
||||||
|
Integer siteId; |
||||||
|
} |
Loading…
Reference in new issue