소스 검색

修复#6100、@IgnoreAuth扫描加速

EightMonth 1 년 전
부모
커밋
faebdee755

+ 2 - 81
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java

@@ -27,6 +27,7 @@ import org.springframework.core.env.Environment;
 import org.springframework.core.type.filter.AnnotationTypeFilter;
 import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StopWatch;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.filter.DelegatingFilterProxy;
@@ -48,7 +49,7 @@ import java.util.*;
 @Slf4j
 @Configuration
 // 免认证注解 @IgnoreAuth 注解生效范围配置
-@ComponentScan(basePackages = {"org.jeecg.**.controller"})
+@ComponentScan(basePackages = {"org.jeecg.**"})
 public class ShiroConfig {
 
     @Resource
@@ -173,16 +174,6 @@ public class ShiroConfig {
         // 企业微信证书排除
         filterChainDefinitionMap.put("/WW_verify*", "anon");
 
-
-        // 通过注解免登录url
-        List<String> ignoreAuthUrlList = collectIgnoreAuthUrl();
-        if (!CollectionUtils.isEmpty(ignoreAuthUrlList)) {
-            for (String url : ignoreAuthUrlList) {
-                filterChainDefinitionMap.put(url, "anon");
-            }
-        }
-
-
         // 添加自己的过滤器并且取名为jwt
         Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
         //如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
@@ -335,74 +326,4 @@ public class ShiroConfig {
         return manager;
     }
 
-
-    @SneakyThrows
-    public List<String> collectIgnoreAuthUrl() {
-        List<String> ignoreAuthUrls = new ArrayList<>();
-        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
-        provider.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
-
-        // 获取当前类的扫描注解的配置
-        Set<BeanDefinition> components = new HashSet<>();
-        for (String basePackage : AnnotationUtils.getAnnotation(ShiroConfig.class, ComponentScan.class).basePackages()) {
-            components.addAll(provider.findCandidateComponents(basePackage));
-        }
-
-        // 逐个匹配获取免认证路径
-        for (BeanDefinition component : components) {
-            String beanClassName = component.getBeanClassName();
-            Class<?> clazz = Class.forName(beanClassName);
-            RequestMapping base = clazz.getAnnotation(RequestMapping.class);
-            String[] baseUrl = {};
-            if (Objects.nonNull(base)) {
-                baseUrl = base.value();
-            }
-            Method[] methods = clazz.getDeclaredMethods();
-
-            for (Method method : methods) {
-                if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(RequestMapping.class)) {
-                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(GetMapping.class)) {
-                    GetMapping requestMapping = method.getAnnotation(GetMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PostMapping.class)) {
-                    PostMapping requestMapping = method.getAnnotation(PostMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PutMapping.class)) {
-                    PutMapping requestMapping = method.getAnnotation(PutMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(DeleteMapping.class)) {
-                    DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PatchMapping.class)) {
-                    PatchMapping requestMapping = method.getAnnotation(PatchMapping.class);
-                    String[] uri = requestMapping.value();
-                    ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
-                }
-            }
-        }
-
-        return ignoreAuthUrls;
-    }
-
-    private List<String> rebuildUrl(String[] bases, String[] uris) {
-        List<String> urls = new ArrayList<>();
-        for (String base : bases) {
-            for (String uri : uris) {
-                urls.add(prefix(base)+prefix(uri));
-            }
-        }
-        return urls;
-    }
-
-    private String prefix(String seg) {
-        return seg.startsWith("/") ? seg : "/"+seg;
-    }
-
 }

+ 5 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java

@@ -8,6 +8,7 @@ import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.util.JwtUtil;
 import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.config.shiro.JwtToken;
+import org.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -47,6 +48,10 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
     @Override
     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
         try {
+            // 判断当前路径是不是注解了@IngoreAuth路径,如果是,则放开验证
+            if (InMemoryIgnoreAuth.contains(((HttpServletRequest) request).getServletPath())) {
+                return true;
+            }
             executeLogin(request, response);
             return true;
         } catch (Exception e) {

+ 104 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/IgnoreAuthPostProcessor.java

@@ -0,0 +1,104 @@
+package org.jeecg.config.shiro.ignore;
+
+import lombok.AllArgsConstructor;
+import org.jeecg.config.shiro.IgnoreAuth;
+import org.springframework.aop.framework.Advised;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StopWatch;
+import org.springframework.web.bind.annotation.*;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * 在spring boot初始化时,根据@RestController注解获取当前spring容器中的bean
+ * @author eightmonth@qq.com
+ * @date 2024/4/18 11:35
+ */
+@Component
+@AllArgsConstructor
+public class IgnoreAuthPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
+
+    private ApplicationContext applicationContext;
+
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        List<String> ignoreAuthUrls = new ArrayList<>();
+        if (event.getApplicationContext().getParent() == null) {
+            // 只处理根应用上下文的事件,避免在子上下文中重复处理
+            Map<String, Object> restControllers = applicationContext.getBeansWithAnnotation(RestController.class);
+            for (Object restController : restControllers.values()) {
+                // 如 online系统的controller并不是spring 默认生成
+                if (restController instanceof Advised) {
+                    ignoreAuthUrls.addAll(postProcessRestController(restController));
+                }
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(ignoreAuthUrls)) {
+            InMemoryIgnoreAuth.set(ignoreAuthUrls);
+        }
+    }
+
+    private List<String> postProcessRestController(Object restController) {
+        List<String> ignoreAuthUrls = new ArrayList<>();
+        Class<?> clazz = ((Advised) restController).getTargetClass();
+        RequestMapping base = clazz.getAnnotation(RequestMapping.class);
+        String[] baseUrl = Objects.nonNull(base) ? base.value() : new String[]{};
+        Method[] methods = clazz.getDeclaredMethods();
+
+        for (Method method : methods) {
+            if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(RequestMapping.class)) {
+                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(GetMapping.class)) {
+                GetMapping requestMapping = method.getAnnotation(GetMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PostMapping.class)) {
+                PostMapping requestMapping = method.getAnnotation(PostMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PutMapping.class)) {
+                PutMapping requestMapping = method.getAnnotation(PutMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(DeleteMapping.class)) {
+                DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            } else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PatchMapping.class)) {
+                PatchMapping requestMapping = method.getAnnotation(PatchMapping.class);
+                String[] uri = requestMapping.value();
+                ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
+            }
+        }
+
+        return ignoreAuthUrls;
+    }
+
+    private List<String> rebuildUrl(String[] bases, String[] uris) {
+        List<String> urls = new ArrayList<>();
+        if (bases.length > 0) {
+            for (String base : bases) {
+                for (String uri : uris) {
+                    urls.add(prefix(base) + prefix(uri));
+                }
+            }
+        } else {
+            Arrays.stream(uris).forEach(uri -> {
+                urls.add(prefix(uri));
+            });
+        }
+        return urls;
+    }
+
+    private String prefix(String seg) {
+        return seg.startsWith("/") ? seg : "/"+seg;
+    }
+}

+ 38 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/InMemoryIgnoreAuth.java

@@ -0,0 +1,38 @@
+package org.jeecg.config.shiro.ignore;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 使用内存存储通过@IgnoreAuth注解的url,配合JwtFilter进行免登录校验
+ * PS:无法使用ThreadLocal进行存储,因为ThreadLocal装载时,JwtFilter已经初始化完毕,导致该类获取ThreadLocal为空
+ * @author eightmonth@qq.com
+ * @date 2024/4/18 15:02
+ */
+public class InMemoryIgnoreAuth {
+    private static final List<String> IGNORE_AUTH_LIST = new ArrayList<>();
+
+    public InMemoryIgnoreAuth() {}
+
+    public static void set(List<String> list) {
+        IGNORE_AUTH_LIST.addAll(list);
+    }
+
+    public static List<String> get() {
+        return IGNORE_AUTH_LIST;
+    }
+
+    public static void clear() {
+        IGNORE_AUTH_LIST.clear();
+    }
+
+    public static boolean contains(String url) {
+        for (String ignoreAuth : IGNORE_AUTH_LIST) {
+            if (url.endsWith(ignoreAuth)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 1 - 0
jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java

@@ -2,6 +2,7 @@ package org.jeecg;
 
 import lombok.extern.slf4j.Slf4j;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.builder.SpringApplicationBuilder;

+ 24 - 0
jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java

@@ -1,13 +1,16 @@
 package org.jeecg.handler.swagger;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import springfox.documentation.swagger.web.*;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * swagger聚合接口,三个接口都是 doc.html需要访问的接口
@@ -19,6 +22,11 @@ import java.util.List;
 public class SwaggerResourceController {
     private MySwaggerResourceProvider swaggerResourceProvider;
 
+    @Autowired
+    private ApplicationContext applicationContext;
+    // 生产环境profile配置模型
+    private static final String PRODUCTION_PROFILE_NAME = "prod*";
+
     @Autowired
     public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
         this.swaggerResourceProvider = swaggerResourceProvider;
@@ -36,6 +44,22 @@ public class SwaggerResourceController {
 
     @RequestMapping
     public ResponseEntity<List<SwaggerResource>> swaggerResources() {
+        // 如果激活的profile带有生产环境的profile,则屏蔽swagger资源
+        if (isProd()) {
+            return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK);
+        }
         return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
     }
+
+    private boolean isProd() {
+        String[] profiles = applicationContext.getEnvironment().getActiveProfiles();
+        Pattern pattern = Pattern.compile(PRODUCTION_PROFILE_NAME);
+        for (String profile : profiles) {
+            if (pattern.matcher(profile).find()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

+ 2 - 0
jeecg-server-cloud/jeecg-system-cloud-start/src/main/java/org/jeecg/JeecgSystemCloudApplication.java

@@ -16,6 +16,7 @@ import org.springframework.core.env.Environment;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
+import java.lang.management.ManagementFactory;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
@@ -35,6 +36,7 @@ public class JeecgSystemCloudApplication extends SpringBootServletInitializer im
     private RedisTemplate<String, Object> redisTemplate;
     @Override
     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+
         return application.sources(JeecgSystemCloudApplication.class);
     }