|
@@ -3,9 +3,7 @@ package com.chelvc.framework.security.interceptor;
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
-import java.lang.reflect.Method;
|
|
|
import java.lang.reflect.Type;
|
|
|
-import java.util.List;
|
|
|
import java.util.Objects;
|
|
|
import java.util.Set;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
@@ -21,25 +19,15 @@ import com.chelvc.framework.base.interceptor.BufferedRequestWrapper;
|
|
|
import com.chelvc.framework.base.util.HttpUtils;
|
|
|
import com.chelvc.framework.base.util.SpringUtils;
|
|
|
import com.chelvc.framework.common.exception.FrameworkException;
|
|
|
-import com.chelvc.framework.common.util.AssertUtils;
|
|
|
import com.chelvc.framework.common.util.CodecUtils;
|
|
|
import com.chelvc.framework.common.util.FileUtils;
|
|
|
-import com.chelvc.framework.common.util.ObjectUtils;
|
|
|
import com.chelvc.framework.common.util.StringUtils;
|
|
|
-import com.chelvc.framework.security.annotation.Authorize;
|
|
|
import com.chelvc.framework.security.annotation.Crypto;
|
|
|
-import com.chelvc.framework.security.annotation.Permission;
|
|
|
-import com.chelvc.framework.security.annotation.Sign;
|
|
|
+import com.chelvc.framework.security.annotation.Security;
|
|
|
import com.chelvc.framework.security.config.SecurityProperties;
|
|
|
import com.chelvc.framework.security.context.SecurityContextHolder;
|
|
|
-import com.google.common.collect.Sets;
|
|
|
-import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.aop.framework.AopProxyUtils;
|
|
|
-import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.boot.context.event.ApplicationStartedEvent;
|
|
|
import org.springframework.context.ApplicationContext;
|
|
|
-import org.springframework.context.ApplicationListener;
|
|
|
import org.springframework.core.MethodParameter;
|
|
|
import org.springframework.core.Ordered;
|
|
|
import org.springframework.http.HttpHeaders;
|
|
@@ -65,12 +53,25 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@ControllerAdvice
|
|
|
-@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
|
|
-public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcConfigurer,
|
|
|
- RequestBodyAdvice, ResponseBodyAdvice<Object>, ApplicationListener<ApplicationStartedEvent> {
|
|
|
- private volatile Set<String> ignores;
|
|
|
+public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcConfigurer, RequestBodyAdvice,
|
|
|
+ ResponseBodyAdvice<Object> {
|
|
|
+ private final Set<String> ignores;
|
|
|
private final SecurityProperties properties;
|
|
|
|
|
|
+ public SecurityValidateInterceptor(ApplicationContext applicationContext) {
|
|
|
+ this.ignores = SecurityContextHolder.getSecurityIgnores(applicationContext);
|
|
|
+ this.properties = applicationContext.getBean(SecurityProperties.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否开启校验观察
|
|
|
+ *
|
|
|
+ * @return true/false
|
|
|
+ */
|
|
|
+ private boolean isObservable() {
|
|
|
+ return ApplicationContextHolder.getProperty("security.validate.observable", boolean.class, false);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 数据加密
|
|
|
*
|
|
@@ -113,103 +114,63 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 判断是否需要忽略安全校验
|
|
|
- *
|
|
|
- * @param request 请求对象
|
|
|
- * @param method 处理方法
|
|
|
- * @return true/false
|
|
|
- */
|
|
|
- private boolean isValidateIgnored(HttpServletRequest request, HandlerMethod method) {
|
|
|
- // 忽略指定接口地址
|
|
|
- if (this.ignores == null) {
|
|
|
- synchronized (this) {
|
|
|
- if (this.ignores == null) {
|
|
|
- this.ignores = SecurityContextHolder.getSecurityIgnores();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (SpringUtils.isPath(request.getRequestURI(), this.ignores)) {
|
|
|
+ @Override
|
|
|
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
|
|
+ throws Exception {
|
|
|
+ if (!(handler instanceof HandlerMethod) || SpringUtils.isPath(request.getRequestURI(), this.ignores)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- // 忽略不需要认证接口地址
|
|
|
- Authorize authorize = method.getMethodAnnotation(Authorize.class);
|
|
|
- if (authorize == null) {
|
|
|
- authorize = method.getBeanType().getAnnotation(Authorize.class);
|
|
|
- }
|
|
|
- return authorize != null && !authorize.enabled();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void onApplicationEvent(ApplicationStartedEvent event) {
|
|
|
- // 检查权限标识是否重复
|
|
|
- ApplicationContext applicationContext = event.getApplicationContext();
|
|
|
- List<Object> controllers = ApplicationContextHolder.lookupControllers(applicationContext);
|
|
|
- if (ObjectUtils.isEmpty(controllers)) {
|
|
|
- return;
|
|
|
+ // 获取安全注解实例
|
|
|
+ HandlerMethod method = (HandlerMethod) handler;
|
|
|
+ Security security = method.getMethodAnnotation(Security.class);
|
|
|
+ if (security == null) {
|
|
|
+ security = method.getBeanType().getAnnotation(Security.class);
|
|
|
}
|
|
|
- Set<String> permissions = Sets.newHashSet();
|
|
|
- controllers.forEach(controller -> {
|
|
|
- Class<?> clazz = AopProxyUtils.ultimateTargetClass(controller);
|
|
|
- for (Method method : clazz.getDeclaredMethods()) {
|
|
|
- Permission annotation = method.getAnnotation(Permission.class);
|
|
|
- if (annotation == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- String value = annotation.value(), group = annotation.group();
|
|
|
- AssertUtils.nonempty(value, () -> "Permission value must not be empty: " + method);
|
|
|
- AssertUtils.nonempty(group, () -> "Permission group must not be empty: " + method);
|
|
|
- AssertUtils.check(permissions.add(value), () -> "Duplicate permission value: " + value);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
|
|
- throws Exception {
|
|
|
- if (!(handler instanceof HandlerMethod) || this.isValidateIgnored(request, (HandlerMethod) handler)) {
|
|
|
+ if (security != null && !security.enabled()) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- // 请求头校验
|
|
|
Session session = SessionContextHolder.getSession();
|
|
|
- if (this.properties.getRequest().isEnableValidateHeader()) {
|
|
|
- if (session.getPlatform() == null || session.getTerminal() == null
|
|
|
- || StringUtils.isEmpty(session.getVersion()) || session.getTimestamp() == null) {
|
|
|
- throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null,
|
|
|
- ApplicationContextHolder.getMessage("Header.Missing"));
|
|
|
+ if (security == null || security.header()) {
|
|
|
+ // 请求头非空校验
|
|
|
+ Long timestamp = session.getTimestamp();
|
|
|
+ if (timestamp == null || session.getPlatform() == null || session.getTerminal() == null
|
|
|
+ || StringUtils.isEmpty(session.getVersion())) {
|
|
|
+ String message = ApplicationContextHolder.getMessage("Header.Missing");
|
|
|
+ if (this.isObservable()) {
|
|
|
+ LoggingContextHolder.warn(log, request, message);
|
|
|
+ } else {
|
|
|
+ throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null, message);
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ // 请求时间戳校验
|
|
|
long expiration = this.properties.getRequest().getExpiration();
|
|
|
- if (expiration > 0 && Math.abs(System.currentTimeMillis() - session.getTimestamp()) > expiration) {
|
|
|
- throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null,
|
|
|
- ApplicationContextHolder.getMessage("Time.Deviated"));
|
|
|
+ if (timestamp != null && expiration > 0 && Math.abs(System.currentTimeMillis() - timestamp) > expiration) {
|
|
|
+ String message = ApplicationContextHolder.getMessage("Time.Deviated");
|
|
|
+ if (this.isObservable()) {
|
|
|
+ LoggingContextHolder.warn(log, request, message);
|
|
|
+ } else {
|
|
|
+ throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null, message);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 数字签名校验
|
|
|
- HandlerMethod method = (HandlerMethod) handler;
|
|
|
- Sign sign = method.getMethodAnnotation(Sign.class);
|
|
|
- if (sign == null) {
|
|
|
- sign = method.getBeanType().getAnnotation(Sign.class);
|
|
|
- }
|
|
|
- if (sign != null && sign.enabled()) {
|
|
|
+ if (security != null && security.sign()) {
|
|
|
String payload = HttpUtils.serialize(request, true);
|
|
|
String signature = request.getHeader(SecurityContextHolder.SIGNATURE);
|
|
|
String ciphertext = SecurityContextHolder.getSecurityCipherHandler().sign(session, payload);
|
|
|
- LoggingContextHolder.debug(log, request, signature, ciphertext, payload);
|
|
|
if (!Objects.equals(ciphertext, signature)) {
|
|
|
- throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null,
|
|
|
- ApplicationContextHolder.getMessage("Signature.Invalid"));
|
|
|
+ String message = ApplicationContextHolder.getMessage("Signature.Invalid");
|
|
|
+ if (this.isObservable()) {
|
|
|
+ LoggingContextHolder.warn(log, request, message, signature, ciphertext, payload);
|
|
|
+ } else {
|
|
|
+ throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null, message);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // 接口权限校验
|
|
|
- Permission permission = method.getMethodAnnotation(Permission.class);
|
|
|
- if (permission != null && !SecurityContextHolder.hashAnyPermission(permission.value(), permission.group())) {
|
|
|
- throw new FrameworkException(HttpStatus.FORBIDDEN.name(), null,
|
|
|
- ApplicationContextHolder.getMessage("Forbidden"));
|
|
|
- }
|
|
|
return true;
|
|
|
}
|
|
|
|