Ver código fonte

优化安全处理逻辑

Woody 2 meses atrás
pai
commit
928b11fcd3

+ 0 - 34
framework-security/src/main/java/com/chelvc/framework/security/annotation/Permission.java

@@ -1,34 +0,0 @@
-package com.chelvc.framework.security.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import com.chelvc.framework.security.context.SecurityContextHolder;
-
-/**
- * 自定义权限校验注解
- *
- * @author Woody
- * @date 2024/5/19
- */
-@Documented
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Permission {
-    /**
-     * 获取权限标识
-     *
-     * @return 权限标识
-     */
-    String value();
-
-    /**
-     * 获取权限分组
-     *
-     * @return 权限分组
-     */
-    String group() default SecurityContextHolder.DEFAULT_PERMISSION_GROUP;
-}

+ 25 - 4
framework-security/src/main/java/com/chelvc/framework/security/annotation/Authorize.java → framework-security/src/main/java/com/chelvc/framework/security/annotation/Security.java

@@ -7,19 +7,40 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * 接口认证注解
+ * 接口安全校验注解
  *
  * @author Woody
- * @date 2024/1/30
+ * @date 2025/3/20
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.TYPE, ElementType.METHOD})
-public @interface Authorize {
+public @interface Security {
     /**
-     * 是否启用
+     * 是否开启签名校验
+     *
+     * @return true/false
+     */
+    boolean sign() default false;
+
+    /**
+     * 是否开启请求头校验
+     *
+     * @return true/false
+     */
+    boolean header() default true;
+
+    /**
+     * 是否开启安全校验
      *
      * @return true/false
      */
     boolean enabled() default true;
+
+    /**
+     * 是否开启认证校验
+     *
+     * @return true/false
+     */
+    boolean authorize() default true;
 }

+ 0 - 25
framework-security/src/main/java/com/chelvc/framework/security/annotation/Sign.java

@@ -1,25 +0,0 @@
-package com.chelvc.framework.security.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * 接口数字签名注解
- *
- * @author Woody
- * @date 2024/8/2
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface Sign {
-    /**
-     * 是否启用
-     *
-     * @return true/false
-     */
-    boolean enabled() default true;
-}

+ 0 - 5
framework-security/src/main/java/com/chelvc/framework/security/config/SecurityProperties.java

@@ -110,10 +110,5 @@ public class SecurityProperties {
          * 使用分类刷新时间间隔(毫秒)
          */
         private long usingRefreshInterval = Using.DEFAULT_NEWLY_INTERVAL;
-
-        /**
-         * 是否开启请求头校验
-         */
-        private boolean enableValidateHeader = true;
     }
 }

+ 4 - 39
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java

@@ -4,16 +4,13 @@ import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Stream;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
-import com.chelvc.framework.base.context.Session;
-import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.base.util.HttpUtils;
 import com.chelvc.framework.base.util.SpringUtils;
 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.Security;
 import com.chelvc.framework.security.config.SecurityProperties;
 import com.chelvc.framework.security.crypto.SecurityCipherHandler;
 import com.google.common.collect.Sets;
@@ -72,11 +69,6 @@ public final class SecurityContextHolder {
      */
     public static final String AUTHORITIES = "authorities";
 
-    /**
-     * 默认权限分组
-     */
-    public static final String DEFAULT_PERMISSION_GROUP = "DEFAULT";
-
     /**
      * 数据安全加解密处理器
      */
@@ -145,33 +137,6 @@ public final class SecurityContextHolder {
         return SECURITY_CIPHER_HANDLER;
     }
 
-    /**
-     * 判断当前用户是否拥有任意权限
-     *
-     * @param permissions 权限标识数组
-     * @return true/false
-     */
-    public static boolean hashAnyPermission(@NonNull String... permissions) {
-        if (ObjectUtils.isEmpty(permissions)) {
-            return true;
-        }
-        Session session = SessionContextHolder.getSession(false);
-        Set<String> authorities = ObjectUtils.ifNull(session, Session::getAuthorities);
-        if (ObjectUtils.notEmpty(authorities)) {
-            Stream<String> stream = Stream.of(permissions);
-            if (stream.anyMatch(authorities::contains)) {
-                return true;
-            }
-            for (String authority : authorities) {
-                Set<?> properties = ApplicationContextHolder.getProperty(authority, Set.class);
-                if (ObjectUtils.notEmpty(properties) && stream.anyMatch(properties::contains)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * 获取排除安全校验接口地址
      *
@@ -237,9 +202,9 @@ public final class SecurityContextHolder {
 
             // 遍历所有接口方法
             for (Method method : clazz.getDeclaredMethods()) {
-                Authorize authorize = method.getAnnotation(Authorize.class);
-                if ((authorize != null || (authorize = clazz.getAnnotation(Authorize.class)) != null)
-                        && !authorize.enabled()) {
+                Security security = method.getAnnotation(Security.class);
+                if ((security != null || (security = clazz.getAnnotation(Security.class)) != null)
+                        && (!security.enabled() || !security.authorize())) {
                     for (String api : SpringUtils.getApis(method)) {
                         ignores.add(HttpUtils.uri(prefix, api));
                     }

+ 33 - 2
framework-security/src/main/java/com/chelvc/framework/security/interceptor/MethodSecurityExpression.java

@@ -1,10 +1,15 @@
 package com.chelvc.framework.security.interceptor;
 
 import java.util.Objects;
+import java.util.Set;
 
+import com.chelvc.framework.base.context.ApplicationContextHolder;
+import com.chelvc.framework.base.context.Session;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.model.Platform;
 import com.chelvc.framework.common.model.Terminal;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import lombok.NonNull;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
@@ -66,12 +71,38 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
 
     @Override
     public boolean hasAuthority(String authority) {
-        return this.root.hasAuthority(authority);
+        return StringUtils.notEmpty(authority) && this.hasAnyAuthority(authority);
     }
 
     @Override
     public boolean hasAnyAuthority(String... authorities) {
-        return this.root.hasAnyAuthority(authorities);
+        if (ObjectUtils.isEmpty(authorities)) {
+            return false;
+        }
+
+        Session session = SessionContextHolder.getSession(false);
+        Set<String> owns = ObjectUtils.ifNull(session, Session::getAuthorities);
+        if (ObjectUtils.isEmpty(owns)) {
+            return false;
+        }
+
+        for (String authority : authorities) {
+            if (StringUtils.notEmpty(authority) && owns.contains(authority)) {
+                return true;
+            }
+        }
+
+        for (String own : owns) {
+            Set<?> properties = ApplicationContextHolder.getProperty(own, Set.class);
+            if (ObjectUtils.notEmpty(properties)) {
+                for (String authority : authorities) {
+                    if (StringUtils.notEmpty(authority) && properties.contains(authority)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
     }
 
     @Override

+ 55 - 94
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SecurityValidateInterceptor.java

@@ -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;
     }