Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

修复#6100、#6169、@IgnoreAuth扫描加速 #6220

Merged
merged 2 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public boolean apply(Object o, String name, Object value) {
if(value!=null && value.toString().length()>length){
return false;
}
if(value instanceof MultipartFile){
return false;
}
return true;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand All @@ -48,7 +49,7 @@
@Slf4j
@Configuration
// 免认证注解 @IgnoreAuth 注解生效范围配置
@ComponentScan(basePackages = {"org.jeecg.**.controller"})
@ComponentScan(basePackages = {"org.jeecg.**"})
public class ShiroConfig {

@Resource
Expand Down Expand Up @@ -173,16 +174,6 @@ public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 企业微信证书排除
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为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
Expand Down Expand Up @@ -335,74 +326,4 @@ public IRedisManager redisManager() {
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -47,6 +48,10 @@ public JwtFilter(boolean allowOrigin){
@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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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需要访问的接口
Expand All @@ -19,6 +22,11 @@
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;
Expand All @@ -36,6 +44,22 @@ public ResponseEntity<UiConfiguration> uiConfiguration() {

@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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;

Expand All @@ -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);
}

Expand Down