Sfoglia il codice sorgente

优化安全处理逻辑

woody 2 mesi fa
parent
commit
71d5caebb6

+ 4 - 52
framework-security/src/main/java/com/chelvc/framework/security/config/OAuthConfigurer.java

@@ -1,40 +1,28 @@
 package com.chelvc.framework.security.config;
 
-import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
-import com.chelvc.framework.base.config.MultiserverMvcConfigurer;
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.LoggingContextHolder;
 import com.chelvc.framework.base.context.Result;
 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.AESUtils;
 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.context.SecurityContextHolder;
 import com.chelvc.framework.security.session.DefaultSessionValidator;
 import com.chelvc.framework.security.session.SessionValidator;
 import com.chelvc.framework.security.session.TokenExpiredValidator;
-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.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
-import org.springframework.core.env.Environment;
-import org.springframework.core.io.Resource;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.authentication.AuthenticationManager;
@@ -143,20 +131,8 @@ public class OAuthConfigurer extends WebSecurityConfigurerAdapter {
 
     @Override
     public void configure(WebSecurity web) {
-        // 排除配置指定、ErrorController、服务心跳检测接口地址
-        Set<String> ignores = Sets.newHashSet(this.properties.getIgnores());
-        Environment environment = this.applicationContext.getEnvironment();
-        ignores.add(environment.resolveRequiredPlaceholders("${server.error.path:${error.path:/error}}"));
-        try {
-            Class<?> clazz =
-                    Class.forName("org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties");
-            ObjectProvider<?> actuator = this.applicationContext.getBeanProvider(clazz);
-            String basePath = (String) ObjectUtils.getObjectValue(actuator.getIfAvailable(), "basePath");
-            if (StringUtils.notEmpty(basePath)) {
-                ignores.add(HttpUtils.uri(basePath, "/**"));
-            }
-        } catch (ClassNotFoundException ignore) {
-        }
+        // 排除不需要安全校验的接口地址
+        Set<String> ignores = SecurityContextHolder.getSecurityIgnores(this.applicationContext);
         if (ObjectUtils.notEmpty(ignores)) {
             web.ignoring().antMatchers(ignores.toArray(StringUtils.EMPTY_ARRAY));
         }
@@ -172,32 +148,8 @@ public class OAuthConfigurer extends WebSecurityConfigurerAdapter {
                 .authenticationEntryPoint(this.authenticationEntryPoint()).jwt().decoder(this.jwtDecoder())
                 .jwtAuthenticationConverter(this.jwtAuthenticationConverter()));
 
-        // 排除不需要认证的业务接口地址
-        Set<String> ignores = Sets.newHashSet();
-        boolean multiserver = this.applicationContext.containsBean(MultiserverMvcConfigurer.class.getName());
-        List<Resource> resources = multiserver ?
-                ApplicationContextHolder.getApplicationResources() : Collections.emptyList();
-        ApplicationContextHolder.lookupControllers(this.applicationContext).forEach(controller -> {
-            Class<?> clazz = AopProxyUtils.ultimateTargetClass(controller);
-
-            // 如果启用多服务MVC配置则将服务名作为接口地址前缀
-            String prefix = null;
-            if (multiserver) {
-                Resource resource = ApplicationContextHolder.lookupClassResource(clazz, resources);
-                prefix = resource == null ? null : ApplicationContextHolder.getApplicationName(resource);
-            }
-
-            // 遍历所有接口方法
-            for (Method method : clazz.getDeclaredMethods()) {
-                Authorize authorize = method.getAnnotation(Authorize.class);
-                if ((authorize != null || (authorize = clazz.getAnnotation(Authorize.class)) != null)
-                        && !authorize.enabled()) {
-                    for (String api : SpringUtils.getApis(method)) {
-                        ignores.add(HttpUtils.uri(prefix, api));
-                    }
-                }
-            }
-        });
+        // 排除不需要认证的接口地址
+        Set<String> ignores = SecurityContextHolder.getAuthorizeIgnores(this.applicationContext);
         if (ObjectUtils.notEmpty(ignores)) {
             http.authorizeRequests().antMatchers(ignores.toArray(StringUtils.EMPTY_ARRAY)).permitAll();
         }

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

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

+ 93 - 0
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java

@@ -1,18 +1,29 @@
 package com.chelvc.framework.security.context;
 
+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.config.MultiserverMvcConfigurer;
 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.config.SecurityProperties;
 import com.chelvc.framework.security.crypto.SecurityCipherHandler;
 import com.google.common.collect.Sets;
 import lombok.NonNull;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.Resource;
 import org.springframework.security.oauth2.jwt.Jwt;
 
 /**
@@ -47,6 +58,11 @@ public final class SecurityContextHolder {
      */
     public static final String CREATING = "creating";
 
+    /**
+     * 数字签名标识
+     */
+    public static final String SIGNATURE = "signature";
+
     /**
      * 主体注册时间戳
      */
@@ -156,4 +172,81 @@ public final class SecurityContextHolder {
         }
         return false;
     }
+
+    /**
+     * 获取排除安全校验接口地址
+     *
+     * @return 接口地址集合
+     */
+    public static Set<String> getSecurityIgnores() {
+        return getSecurityIgnores(ApplicationContextHolder.getApplicationContext());
+    }
+
+    /**
+     * 获取排除安全校验接口地址
+     *
+     * @param applicationContext 应用上下文
+     * @return 接口地址集合
+     */
+    public static Set<String> getSecurityIgnores(@NonNull ApplicationContext applicationContext) {
+        SecurityProperties properties = applicationContext.getBean(SecurityProperties.class);
+        Set<String> ignores = Sets.newHashSet(properties.getIgnores());
+        Environment environment = applicationContext.getEnvironment();
+        ignores.add(environment.resolveRequiredPlaceholders("${server.error.path:${error.path:/error}}"));
+        try {
+            Class<?> clazz =
+                    Class.forName("org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties");
+            ObjectProvider<?> actuator = applicationContext.getBeanProvider(clazz);
+            String basePath = (String) ObjectUtils.getObjectValue(actuator.getIfAvailable(), "basePath");
+            if (StringUtils.notEmpty(basePath)) {
+                ignores.add(HttpUtils.uri(basePath, "/**"));
+            }
+        } catch (ClassNotFoundException ignore) {
+        }
+        return ignores;
+    }
+
+    /**
+     * 获取排除认证接口地址
+     *
+     * @return 接口地址集合
+     */
+    public static Set<String> getAuthorizeIgnores() {
+        return getAuthorizeIgnores(ApplicationContextHolder.getApplicationContext());
+    }
+
+    /**
+     * 获取排除认证接口地址
+     *
+     * @param applicationContext 应用上下文
+     * @return 接口地址集合
+     */
+    public static Set<String> getAuthorizeIgnores(@NonNull ApplicationContext applicationContext) {
+        Set<String> ignores = Sets.newHashSet();
+        boolean multiserver = applicationContext.containsBean(MultiserverMvcConfigurer.class.getName());
+        List<Resource> resources = multiserver ?
+                ApplicationContextHolder.getApplicationResources() : Collections.emptyList();
+        ApplicationContextHolder.lookupControllers(applicationContext).forEach(controller -> {
+            Class<?> clazz = AopProxyUtils.ultimateTargetClass(controller);
+
+            // 如果启用多服务MVC配置则将服务名作为接口地址前缀
+            String prefix = null;
+            if (multiserver) {
+                Resource resource = ApplicationContextHolder.lookupClassResource(clazz, resources);
+                prefix = resource == null ? null : ApplicationContextHolder.getApplicationName(resource);
+            }
+
+            // 遍历所有接口方法
+            for (Method method : clazz.getDeclaredMethods()) {
+                Authorize authorize = method.getAnnotation(Authorize.class);
+                if ((authorize != null || (authorize = clazz.getAnnotation(Authorize.class)) != null)
+                        && !authorize.enabled()) {
+                    for (String api : SpringUtils.getApis(method)) {
+                        ignores.add(HttpUtils.uri(prefix, api));
+                    }
+                }
+            }
+        });
+        return ObjectUtils.isEmpty(ignores) ? Collections.emptySet() : ignores;
+    }
 }

+ 43 - 21
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SecurityValidateInterceptor.java

@@ -26,6 +26,7 @@ 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;
@@ -67,11 +68,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 @RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcConfigurer,
         RequestBodyAdvice, ResponseBodyAdvice<Object>, ApplicationListener<ApplicationStartedEvent> {
-    /**
-     * 签名信息请求头
-     */
-    private static final String HEADER_SIGNATURE = "signature";
-
+    private volatile Set<String> ignores;
     private final SecurityProperties properties;
 
     /**
@@ -116,6 +113,34 @@ 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)) {
+            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) {
         // 检查权限标识是否重复
@@ -143,26 +168,23 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
-        // 忽略地址处理
-        if (SpringUtils.isPath(request.getRequestURI(), this.properties.getIgnores())) {
+        if (!(handler instanceof HandlerMethod) || this.isValidateIgnored(request, (HandlerMethod) handler)) {
             return true;
         }
 
         // 请求头校验
         Session session = SessionContextHolder.getSession();
-        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"));
-        }
-        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 (!(handler instanceof HandlerMethod)) {
-            return true;
+        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"));
+            }
+            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"));
+            }
         }
 
         // 数字签名校验
@@ -173,7 +195,7 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
         }
         if (sign != null && sign.enabled()) {
             String payload = HttpUtils.serialize(request, true);
-            String signature = request.getHeader(HEADER_SIGNATURE);
+            String signature = request.getHeader(SecurityContextHolder.SIGNATURE);
             String ciphertext = SecurityContextHolder.getSecurityCipherHandler().sign(session, payload);
             LoggingContextHolder.debug(log, signature, ciphertext, payload);
             if (!Objects.equals(ciphertext, signature)) {