refactor: 移除匿名访问的设计,还原常用的白名单配置方案
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.youlai</groupId>
|
<groupId>com.youlai</groupId>
|
||||||
<artifactId>youlai-boot</artifactId>
|
<artifactId>youlai-boot</artifactId>
|
||||||
<version>2.18.1</version>
|
<version>2.19.0</version>
|
||||||
<description>基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。</description>
|
<description>基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/// 标记匿名访问
|
|
||||||
@Inherited
|
|
||||||
@Documented
|
|
||||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface AnonymousAccess {
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation.methods;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for mapping HTTP {@code DELETE} requests onto specific handler
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* 支持匿名访问 DeleteMapping
|
|
||||||
*
|
|
||||||
* @see RequestMapping
|
|
||||||
*/
|
|
||||||
@AnonymousAccess
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@RequestMapping(method = RequestMethod.DELETE)
|
|
||||||
public @interface AnonymousDeleteMapping {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#name}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String name() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#path}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] path() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#params}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#headers}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] headers() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#consumes}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] consumes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#produces}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] produces() default {};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation.methods;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for mapping HTTP {@code GET} requests onto specific handler
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* 支持匿名访问 GetMapping
|
|
||||||
*
|
|
||||||
* @see RequestMapping
|
|
||||||
*/
|
|
||||||
@AnonymousAccess
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@RequestMapping(method = RequestMethod.GET)
|
|
||||||
public @interface AnonymousGetMapping {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#name}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String name() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#path}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] path() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#params}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#headers}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] headers() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#consumes}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] consumes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#produces}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] produces() default {};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation.methods;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for mapping HTTP {@code PATCH} requests onto specific handler
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* 支持匿名访问 PatchMapping
|
|
||||||
*
|
|
||||||
* @see RequestMapping
|
|
||||||
*/
|
|
||||||
@AnonymousAccess
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@RequestMapping(method = RequestMethod.PATCH)
|
|
||||||
public @interface AnonymousPatchMapping {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#name}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String name() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#path}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] path() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#params}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#headers}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] headers() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#consumes}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] consumes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#produces}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] produces() default {};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation.methods;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for mapping HTTP {@code POST} requests onto specific handler
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* 支持匿名访问 PostMapping
|
|
||||||
*
|
|
||||||
* @see RequestMapping
|
|
||||||
*/
|
|
||||||
@AnonymousAccess
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@RequestMapping(method = RequestMethod.POST)
|
|
||||||
public @interface AnonymousPostMapping {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#name}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String name() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#path}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] path() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#params}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#headers}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] headers() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#consumes}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] consumes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#produces}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] produces() default {};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.youlai.boot.common.annotation.methods;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for mapping HTTP {@code PUT} requests onto specific handler
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* 支持匿名访问 PutMapping
|
|
||||||
*
|
|
||||||
* @see RequestMapping
|
|
||||||
*/
|
|
||||||
@AnonymousAccess
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@RequestMapping(method = RequestMethod.PUT)
|
|
||||||
public @interface AnonymousPutMapping {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#name}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String name() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#path}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] path() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#params}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] params() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#headers}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] headers() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#consumes}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] consumes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#produces}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class)
|
|
||||||
String[] produces() default {};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package com.youlai.boot.common.util;
|
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.AnonymousAccess;
|
|
||||||
import com.youlai.boot.common.enums.RequestMethodEnum;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.method.HandlerMethod;
|
|
||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class AnonymousUtils {
|
|
||||||
/**
|
|
||||||
* 获取所有匿名标记URL,不区分请求方式
|
|
||||||
*/
|
|
||||||
public static Set<String> getAnonymousUrls(ApplicationContext applicationContext) {
|
|
||||||
return getAllAnonymousUrls(applicationContext).values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有被标记的匿名类集合
|
|
||||||
*
|
|
||||||
* @return /
|
|
||||||
*/
|
|
||||||
public static Map<String, Set<String>> getAllAnonymousUrls(ApplicationContext applicationContext) {
|
|
||||||
// 搜索匿名标记
|
|
||||||
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
|
|
||||||
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
|
||||||
|
|
||||||
// 获取所以被标记的匿名类集合
|
|
||||||
return getAnonymousUrl(handlerMethodMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有被标记的匿名类集合
|
|
||||||
*
|
|
||||||
* @param handlerMethodMap 请求映射信息集合
|
|
||||||
* @return /
|
|
||||||
*/
|
|
||||||
public static Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
|
|
||||||
Set<String> get = new HashSet<>();
|
|
||||||
Set<String> post = new HashSet<>();
|
|
||||||
Set<String> put = new HashSet<>();
|
|
||||||
Set<String> patch = new HashSet<>();
|
|
||||||
Set<String> delete = new HashSet<>();
|
|
||||||
Set<String> all = new HashSet<>();
|
|
||||||
|
|
||||||
handlerMethodMap.forEach((key, value) -> {
|
|
||||||
AnonymousAccess anonymousAccess = value.getMethodAnnotation(AnonymousAccess.class);
|
|
||||||
if (anonymousAccess != null) {
|
|
||||||
ArrayList<RequestMethod> requestMethods = new ArrayList<>(key.getMethodsCondition().getMethods());
|
|
||||||
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
|
|
||||||
switch (Objects.requireNonNull(request)) {
|
|
||||||
case GET:
|
|
||||||
get.addAll(key.getDirectPaths());
|
|
||||||
break;
|
|
||||||
case POST:
|
|
||||||
post.addAll(key.getDirectPaths());
|
|
||||||
break;
|
|
||||||
case PUT:
|
|
||||||
put.addAll(key.getDirectPaths());
|
|
||||||
break;
|
|
||||||
case PATCH:
|
|
||||||
patch.addAll(key.getDirectPaths());
|
|
||||||
break;
|
|
||||||
case DELETE:
|
|
||||||
delete.addAll(key.getDirectPaths());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
all.addAll(key.getDirectPaths());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Map.ofEntries(
|
|
||||||
entry(RequestMethodEnum.GET.getType(), get),
|
|
||||||
entry(RequestMethodEnum.POST.getType(), post),
|
|
||||||
entry(RequestMethodEnum.PUT.getType(), put),
|
|
||||||
entry(RequestMethodEnum.PATCH.getType(), patch),
|
|
||||||
entry(RequestMethodEnum.DELETE.getType(), delete),
|
|
||||||
entry(RequestMethodEnum.ALL.getType(), all)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map.Entry<String, Set<String>> entry(String key, Collection<String> collection) {
|
|
||||||
return Map.entry(key, collection.stream().filter(it -> !it.isEmpty()).collect(Collectors.toUnmodifiableSet()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,7 @@ package com.youlai.boot.config;
|
|||||||
|
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
import cn.hutool.captcha.generator.CodeGenerator;
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import com.youlai.boot.common.enums.RequestMethodEnum;
|
|
||||||
import com.youlai.boot.common.util.AnonymousUtils;
|
|
||||||
import com.youlai.boot.config.property.SecurityProperties;
|
import com.youlai.boot.config.property.SecurityProperties;
|
||||||
import com.youlai.boot.core.filter.RateLimiterFilter;
|
import com.youlai.boot.core.filter.RateLimiterFilter;
|
||||||
import com.youlai.boot.core.security.exception.MyAccessDeniedHandler;
|
import com.youlai.boot.core.security.exception.MyAccessDeniedHandler;
|
||||||
@@ -17,11 +15,9 @@ import com.youlai.boot.shared.auth.service.impl.JwtTokenService;
|
|||||||
import com.youlai.boot.system.service.ConfigService;
|
import com.youlai.boot.system.service.ConfigService;
|
||||||
import com.youlai.boot.system.service.UserService;
|
import com.youlai.boot.system.service.UserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
@@ -36,9 +32,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 安全配置
|
* Spring Security 安全配置
|
||||||
*
|
*
|
||||||
@@ -60,64 +53,61 @@ public class SecurityConfig {
|
|||||||
private final SysUserDetailsService userDetailsService;
|
private final SysUserDetailsService userDetailsService;
|
||||||
|
|
||||||
private final CodeGenerator codeGenerator;
|
private final CodeGenerator codeGenerator;
|
||||||
private final SecurityProperties securityProperties;
|
|
||||||
private final ConfigService configService;
|
private final ConfigService configService;
|
||||||
|
private final SecurityProperties securityProperties;
|
||||||
|
|
||||||
private final MyAuthenticationEntryPoint authenticationEntryPoint; // 项目内安全类
|
/**
|
||||||
private final MyAccessDeniedHandler accessDeniedHandler;
|
* 配置安全过滤链 SecurityFilterChain
|
||||||
|
*/
|
||||||
private final ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
// 获取所有匿名访问路径
|
return http
|
||||||
Map<String, Set<String>> anonymousUrls = AnonymousUtils.getAllAnonymousUrls(applicationContext);
|
.authorizeHttpRequests(requestMatcherRegistry -> {
|
||||||
|
// 忽略认证的 URI 地址
|
||||||
http
|
String[] ignoreUrls = securityProperties.getIgnoreUrls();
|
||||||
|
if (ArrayUtil.isNotEmpty(ignoreUrls)) {
|
||||||
.authorizeHttpRequests(requestMatcherRegistry ->
|
requestMatcherRegistry.requestMatchers(ignoreUrls).permitAll();
|
||||||
requestMatcherRegistry
|
}
|
||||||
// GET
|
// 其他请求都需要认证
|
||||||
.requestMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
|
requestMatcherRegistry.anyRequest().authenticated();
|
||||||
// POST
|
}
|
||||||
.requestMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
|
|
||||||
// PUT
|
|
||||||
.requestMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
|
|
||||||
// PATCH
|
|
||||||
.requestMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
|
|
||||||
// DELETE
|
|
||||||
.requestMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
|
|
||||||
// 所有类型的接口都放行
|
|
||||||
.requestMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
)
|
||||||
.exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
|
.exceptionHandling(configurer ->
|
||||||
httpSecurityExceptionHandlingConfigurer
|
configurer
|
||||||
.authenticationEntryPoint(authenticationEntryPoint)
|
.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未认证异常处理器
|
||||||
.accessDeniedHandler(accessDeniedHandler)
|
.accessDeniedHandler(new MyAccessDeniedHandler()) // 无权限访问异常处理器
|
||||||
)
|
)
|
||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
// 禁用默认的 Spring Security 特性,适用于前后端分离架构
|
||||||
|
.sessionManagement(configurer ->
|
||||||
|
configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态认证,不使用 Session
|
||||||
|
)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 防护,前后端分离无需此防护机制
|
||||||
|
.formLogin(AbstractHttpConfigurer::disable) // 禁用默认的表单登录功能,前后端分离采用 Token 认证方式
|
||||||
|
.httpBasic(AbstractHttpConfigurer::disable) // 禁用 HTTP Basic 认证,避免弹窗式登录
|
||||||
|
// 禁用 X-Frame-Options 响应头,允许页面被嵌套到 iframe 中
|
||||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||||
// 限流过滤器
|
// 限流过滤器
|
||||||
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
||||||
// 验证码校验过滤器
|
// 验证码校验过滤器
|
||||||
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
||||||
// JWT 验证和解析过滤器
|
// JWT 验证和解析过滤器
|
||||||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenService), UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenService), UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.build();
|
||||||
return http.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不走过滤器链的放行配置
|
* 配置Web安全自定义器,以忽略特定请求路径的安全性检查。
|
||||||
|
* <p>
|
||||||
|
* 该配置用于指定哪些请求路径不经过Spring Security过滤器链。通常用于静态资源文件。
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
return (web) -> {
|
return (web) -> {
|
||||||
if (CollectionUtil.isNotEmpty(securityProperties.getIgnoreUrls())) {
|
String[] unsecuredUrls = securityProperties.getUnsecuredUrls();
|
||||||
web.ignoring().requestMatchers(securityProperties.getIgnoreUrls().toArray(new String[0]));
|
if (ArrayUtil.isNotEmpty(unsecuredUrls)) {
|
||||||
|
web.ignoring().requestMatchers(unsecuredUrls);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -130,7 +120,6 @@ public class SecurityConfig {
|
|||||||
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||||
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
|
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
|
||||||
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
|
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
|
||||||
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
|
|
||||||
return daoAuthenticationProvider;
|
return daoAuthenticationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ public class SecurityProperties {
|
|||||||
/**
|
/**
|
||||||
* 白名单 URL 集合
|
* 白名单 URL 集合
|
||||||
*/
|
*/
|
||||||
private List<String> ignoreUrls;
|
private String[] ignoreUrls;
|
||||||
|
|
||||||
|
private String[] unsecuredUrls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会话属性
|
* 会话属性
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import cn.hutool.http.useragent.UserAgentUtil;
|
|||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.aliyun.oss.HttpMethod;
|
import com.aliyun.oss.HttpMethod;
|
||||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||||
import com.youlai.boot.common.util.AnonymousUtils;
|
|
||||||
import com.youlai.boot.common.util.IPUtils;
|
import com.youlai.boot.common.util.IPUtils;
|
||||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||||
import com.youlai.boot.system.model.entity.Log;
|
import com.youlai.boot.system.model.entity.Log;
|
||||||
@@ -22,7 +21,6 @@ import org.aspectj.lang.annotation.AfterReturning;
|
|||||||
import org.aspectj.lang.annotation.AfterThrowing;
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
@@ -31,12 +29,11 @@ import org.springframework.web.servlet.HandlerMapping;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志切面
|
* 日志切面
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 2024/6/25
|
* @since 2024/6/25
|
||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
@@ -46,7 +43,6 @@ import java.util.Set;
|
|||||||
public class LogAspect {
|
public class LogAspect {
|
||||||
private final LogService logService;
|
private final LogService logService;
|
||||||
private final HttpServletRequest request;
|
private final HttpServletRequest request;
|
||||||
private final ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.youlai.boot.common.annotation.Log)")
|
@Pointcut("@annotation(com.youlai.boot.common.annotation.Log)")
|
||||||
public void logPointcut() {
|
public void logPointcut() {
|
||||||
@@ -80,16 +76,6 @@ public class LogAspect {
|
|||||||
private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, com.youlai.boot.common.annotation.Log logAnnotation) {
|
private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, com.youlai.boot.common.annotation.Log logAnnotation) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
|
|
||||||
Long userId = null;
|
|
||||||
|
|
||||||
// 获取所有匿名标记
|
|
||||||
Set<String> anonymousUrls = AnonymousUtils.getAnonymousUrls(applicationContext);
|
|
||||||
|
|
||||||
// 非登录请求获取用户ID,登录请求在登录成功后(joinPoint.proceed())获取用户ID
|
|
||||||
if (!anonymousUrls.contains(requestURI)) {
|
|
||||||
userId = SecurityUtils.getUserId();
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeInterval timer = DateUtil.timer();
|
TimeInterval timer = DateUtil.timer();
|
||||||
// 执行方法
|
// 执行方法
|
||||||
long executionTime = timer.interval();
|
long executionTime = timer.interval();
|
||||||
@@ -113,12 +99,8 @@ public class LogAspect {
|
|||||||
log.setResponseContent(JSONUtil.toJsonStr(jsonResult));
|
log.setResponseContent(JSONUtil.toJsonStr(jsonResult));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.setRequestUri(requestURI);
|
log.setRequestUri(requestURI);
|
||||||
// 登录方法需要在登录成功后获取用户ID
|
Long userId = SecurityUtils.getUserId();
|
||||||
if (userId == null) {
|
|
||||||
userId = SecurityUtils.getUserId();
|
|
||||||
}
|
|
||||||
log.setCreateBy(userId);
|
log.setCreateBy(userId);
|
||||||
String ipAddr = IPUtils.getIpAddr(request);
|
String ipAddr = IPUtils.getIpAddr(request);
|
||||||
if (StrUtil.isNotBlank(ipAddr)) {
|
if (StrUtil.isNotBlank(ipAddr)) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.youlai.boot.shared.auth.controller;
|
package com.youlai.boot.shared.auth.controller;
|
||||||
|
|
||||||
import com.youlai.boot.common.annotation.methods.AnonymousPostMapping;
|
|
||||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||||
import com.youlai.boot.common.result.Result;
|
import com.youlai.boot.common.result.Result;
|
||||||
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
|
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
|
||||||
@@ -31,7 +30,7 @@ public class AuthController {
|
|||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
|
|
||||||
@Operation(summary = "登录")
|
@Operation(summary = "登录")
|
||||||
@AnonymousPostMapping("/login")
|
@PostMapping("/login")
|
||||||
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
||||||
public Result<AuthTokenResponse> login(
|
public Result<AuthTokenResponse> login(
|
||||||
@Parameter(description = "用户名", example = "admin") @RequestParam String username,
|
@Parameter(description = "用户名", example = "admin") @RequestParam String username,
|
||||||
@@ -59,12 +58,12 @@ public class AuthController {
|
|||||||
@Operation(summary = "刷新token")
|
@Operation(summary = "刷新token")
|
||||||
@PostMapping("/refresh-token")
|
@PostMapping("/refresh-token")
|
||||||
public Result<?> refreshToken(@RequestBody RefreshTokenRequest request) {
|
public Result<?> refreshToken(@RequestBody RefreshTokenRequest request) {
|
||||||
AuthTokenResponse authTokenResponse = authService.refreshToken(request);
|
AuthTokenResponse authTokenResponse = authService.refreshToken(request);
|
||||||
return Result.success(authTokenResponse);
|
return Result.success(authTokenResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "微信登录")
|
@Operation(summary = "微信登录")
|
||||||
@AnonymousPostMapping("/wechat-login")
|
@PostMapping("/wechat-login")
|
||||||
@Log(value = "微信登录", module = LogModuleEnum.LOGIN)
|
@Log(value = "微信登录", module = LogModuleEnum.LOGIN)
|
||||||
public Result<AuthTokenResponse> wechatLogin(
|
public Result<AuthTokenResponse> wechatLogin(
|
||||||
@Parameter(description = "微信授权码", example = "code") @RequestParam String code
|
@Parameter(description = "微信授权码", example = "code") @RequestParam String code
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ mybatis-plus:
|
|||||||
# 主键ID类型
|
# 主键ID类型
|
||||||
id-type: none
|
id-type: none
|
||||||
# 逻辑删除全局属性名(驼峰和下划线都支持)
|
# 逻辑删除全局属性名(驼峰和下划线都支持)
|
||||||
logic-delete-field: isDeleted
|
logic-delete-field: is_deleted
|
||||||
# 逻辑删除-删除值
|
# 逻辑删除-删除值
|
||||||
logic-delete-value: 1
|
logic-delete-value: 1
|
||||||
# 逻辑删除-未删除值
|
# 逻辑删除-未删除值
|
||||||
@@ -85,17 +85,20 @@ security:
|
|||||||
access-token-time-to-live: 3600
|
access-token-time-to-live: 3600
|
||||||
# 刷新令牌有效期(单位:秒),默认 7 天
|
# 刷新令牌有效期(单位:秒),默认 7 天
|
||||||
refresh-token-time-to-live: 604800
|
refresh-token-time-to-live: 604800
|
||||||
# 白名单列表
|
# 无需认证的请求路径
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
- /v3/api-docs/**
|
- /api/v1/auth/login # 用户登录接口
|
||||||
- /doc.html
|
- /api/v1/auth/wechat-login # 微信登录接口
|
||||||
|
- /api/v1/auth/captcha # 验证码获取接口
|
||||||
|
- /api/v1/auth/refresh-token # 刷新令牌接口
|
||||||
|
- /ws/** # WebSocket接口
|
||||||
|
# 不走 Spring Security 过滤器链的请求路径(一般是静态资源)
|
||||||
|
unsecured-urls:
|
||||||
- ${springdoc.swagger-ui.path}
|
- ${springdoc.swagger-ui.path}
|
||||||
- /swagger-resources/**
|
- /doc.html
|
||||||
|
- /v3/api-docs/**
|
||||||
- /webjars/**
|
- /webjars/**
|
||||||
- /swagger-ui/**
|
|
||||||
- /api/v1/auth/captcha
|
|
||||||
- /api/v1/auth/refresh-token
|
|
||||||
- /ws/**
|
|
||||||
|
|
||||||
# 文件存储配置
|
# 文件存储配置
|
||||||
oss:
|
oss:
|
||||||
|
|||||||
@@ -84,17 +84,19 @@ security:
|
|||||||
access-token-time-to-live: 3600
|
access-token-time-to-live: 3600
|
||||||
# 刷新令牌有效期(单位:秒),默认 7 天
|
# 刷新令牌有效期(单位:秒),默认 7 天
|
||||||
refresh-token-time-to-live: 604800
|
refresh-token-time-to-live: 604800
|
||||||
# 白名单列表
|
# 无需认证的请求路径
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
- /v3/api-docs/**
|
- /api/v1/auth/login # 用户登录接口
|
||||||
- /doc.html
|
- /api/v1/auth/wechat-login # 微信登录接口
|
||||||
|
- /api/v1/auth/captcha # 验证码获取接口
|
||||||
|
- /api/v1/auth/refresh-token # 刷新令牌接口
|
||||||
|
- /ws/** # WebSocket接口
|
||||||
|
# 不走 Spring Security 过滤器链的请求路径(一般是静态资源)
|
||||||
|
unsecured-urls:
|
||||||
- ${springdoc.swagger-ui.path}
|
- ${springdoc.swagger-ui.path}
|
||||||
- /swagger-resources/**
|
- /doc.html
|
||||||
|
- /v3/api-docs/**
|
||||||
- /webjars/**
|
- /webjars/**
|
||||||
- /swagger-ui/**
|
|
||||||
- /api/v1/auth/captcha
|
|
||||||
- /api/v1/auth/refresh-token
|
|
||||||
- /ws/**
|
|
||||||
|
|
||||||
# 文件存储配置
|
# 文件存储配置
|
||||||
oss:
|
oss:
|
||||||
|
|||||||
Reference in New Issue
Block a user