diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java index f226864f..00d4151f 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java +++ b/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 ignoreAuthUrlList = collectIgnoreAuthUrl(); - if (!CollectionUtils.isEmpty(ignoreAuthUrlList)) { - for (String url : ignoreAuthUrlList) { - filterChainDefinitionMap.put(url, "anon"); - } - } - - // 添加自己的过滤器并且取名为jwt Map filterMap = new HashMap(1); //如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】 @@ -335,74 +326,4 @@ public class ShiroConfig { return manager; } - - @SneakyThrows - public List collectIgnoreAuthUrl() { - List ignoreAuthUrls = new ArrayList<>(); - ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); - provider.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); - - // 获取当前类的扫描注解的配置 - Set 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 rebuildUrl(String[] bases, String[] uris) { - List 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; - } - } diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java index 2e527c07..8f29dd28 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java +++ b/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) { diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/IgnoreAuthPostProcessor.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/IgnoreAuthPostProcessor.java new file mode 100644 index 00000000..1be83b5f --- /dev/null +++ b/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 { + + private ApplicationContext applicationContext; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + List ignoreAuthUrls = new ArrayList<>(); + if (event.getApplicationContext().getParent() == null) { + // 只处理根应用上下文的事件,避免在子上下文中重复处理 + Map 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 postProcessRestController(Object restController) { + List 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 rebuildUrl(String[] bases, String[] uris) { + List 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; + } +} diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/InMemoryIgnoreAuth.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ignore/InMemoryIgnoreAuth.java new file mode 100644 index 00000000..4c6788cb --- /dev/null +++ b/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 IGNORE_AUTH_LIST = new ArrayList<>(); + + public InMemoryIgnoreAuth() {} + + public static void set(List list) { + IGNORE_AUTH_LIST.addAll(list); + } + + public static List 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; + } +} diff --git a/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java b/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java index 633249de..d3c7e091 100644 --- a/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java +++ b/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; diff --git a/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java b/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java index 41424fbe..be7667cc 100644 --- a/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java +++ b/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> 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; + } } \ No newline at end of file diff --git a/jeecg-server-cloud/jeecg-system-cloud-start/src/main/java/org/jeecg/JeecgSystemCloudApplication.java b/jeecg-server-cloud/jeecg-system-cloud-start/src/main/java/org/jeecg/JeecgSystemCloudApplication.java index 4e37d4d8..dc741e31 100644 --- a/jeecg-server-cloud/jeecg-system-cloud-start/src/main/java/org/jeecg/JeecgSystemCloudApplication.java +++ b/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 redisTemplate; @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(JeecgSystemCloudApplication.class); }