1. 【新增】接口支持通过 @PermitAll 注解,允许匿名(未登录)进行访问

2. 【新增】`yudao.security.permit-all-urls` 配置项,允许匿名(未登录)进行访问
This commit is contained in:
YunaiV
2022-07-06 21:45:25 +08:00
parent 15ee097a8b
commit d9c2da7c91
17 changed files with 102 additions and 86 deletions

View File

@ -6,6 +6,8 @@ import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
@ConfigurationProperties(prefix = "yudao.security")
@Validated
@ -30,4 +32,9 @@ public class SecurityProperties {
@NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
private String mockSecret = "test";
/**
* 免登录的 URL 列表
*/
private List<String> permitAllUrls = Collections.emptyList();
}

View File

@ -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;
}
}