|
@@ -2,7 +2,10 @@ package cn.iocoder.yudao.framework.security.config;
|
|
|
|
|
|
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
|
|
|
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
|
|
+import com.google.common.collect.HashMultimap;
|
|
|
+import com.google.common.collect.Multimap;
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
|
+import org.springframework.context.ApplicationContext;
|
|
|
import org.springframework.context.annotation.Bean;
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
import org.springframework.http.HttpMethod;
|
|
@@ -14,9 +17,15 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
|
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
|
+import org.springframework.web.method.HandlerMethod;
|
|
|
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
|
|
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import javax.annotation.security.PermitAll;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
/**
|
|
|
* 自定义的 Spring Security 配置适配器实现
|
|
@@ -29,6 +38,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|
|
|
|
|
@Resource
|
|
|
private WebProperties webProperties;
|
|
|
+ @Resource
|
|
|
+ private SecurityProperties securityProperties;
|
|
|
|
|
|
/**
|
|
|
* 认证失败处理类 Bean
|
|
@@ -54,6 +65,9 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|
|
@Resource
|
|
|
private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private ApplicationContext applicationContext;
|
|
|
+
|
|
|
/**
|
|
|
* 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入
|
|
|
* 通过覆写父类的该方法,添加 @Bean 注解,解决该问题
|
|
@@ -98,13 +112,21 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|
|
.accessDeniedHandler(accessDeniedHandler);
|
|
|
// 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
|
|
|
|
|
|
+ // 获得 @PermitAll 带来的 URL 列表,免登录
|
|
|
+ Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
|
|
|
// 设置每个请求的权限
|
|
|
httpSecurity
|
|
|
// ①:全局共享规则
|
|
|
.authorizeRequests()
|
|
|
// 静态资源,可匿名访问
|
|
|
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
|
|
|
- .antMatchers(HttpMethod.GET, "/admin-ui/**").permitAll()
|
|
|
+ // 设置 @PermitAll 无需认证
|
|
|
+ .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
|
|
|
+ .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
|
|
|
+ .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
|
|
|
+ .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
|
|
|
+ // 基于 yudao.security.permit-all-urls 无需认证
|
|
|
+ .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
|
|
|
// 设置 App API 无需认证
|
|
|
.antMatchers(buildAppApi("/**")).permitAll()
|
|
|
// ②:每个项目的自定义规则
|
|
@@ -118,9 +140,46 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|
|
// 添加 JWT Filter
|
|
|
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private String buildAppApi(String url) {
|
|
|
return webProperties.getAppApi().getPrefix() + url;
|
|
|
}
|
|
|
|
|
|
+ private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
|
|
|
+ Multimap<HttpMethod, String> result = HashMultimap.create();
|
|
|
+ // 获得接口对应的 HandlerMethod 集合
|
|
|
+ RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
|
|
|
+ applicationContext.getBean("requestMappingHandlerMapping");
|
|
|
+ Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
|
|
+ // 获得有 @PermitAll 注解的接口
|
|
|
+ for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
|
|
|
+ HandlerMethod handlerMethod = entry.getValue();
|
|
|
+ if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (entry.getKey().getPatternsCondition() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Set<String> urls = entry.getKey().getPatternsCondition().getPatterns();
|
|
|
+ // 根据请求方法,添加到 result 结果
|
|
|
+ entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {
|
|
|
+ switch (requestMethod) {
|
|
|
+ case GET:
|
|
|
+ result.putAll(HttpMethod.GET, urls);
|
|
|
+ break;
|
|
|
+ case POST:
|
|
|
+ result.putAll(HttpMethod.POST, urls);
|
|
|
+ break;
|
|
|
+ case PUT:
|
|
|
+ result.putAll(HttpMethod.PUT, urls);
|
|
|
+ break;
|
|
|
+ case DELETE:
|
|
|
+ result.putAll(HttpMethod.DELETE, urls);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
}
|