From 1940b29247764af2e38212745126b4c26eac2785 Mon Sep 17 00:00:00 2001 From: bunny <1319900154@qq.com> Date: Sat, 19 Jul 2025 23:49:01 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E4=BD=BF=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=B8=A6=E7=9A=84=E6=A0=A1=E9=AA=8C=E6=96=B9=E6=B3=95,?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9D=83=E9=99=90=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-security/ReadMe.md | 127 +++++++++++++++++- .../SecurityPreAuthorizationController.java | 4 +- .../AuthorizationLogic.java | 2 +- .../AuthorizationManagerConfiguration.java | 16 ++- .../config/SecurityWebConfiguration.java | 2 +- .../properties/SecurityConfigProperties.java | 2 +- .../filter/JwtAuthenticationFilter.java | 2 +- .../manger/PostAuthorizationManager.java | 4 +- .../manger/PreAuthorizationManager.java | 106 --------------- .../manger/demo1/PreAuthorizationManager.java | 106 +++++++++++++++ .../step3/security/manger/demo1/ReadMe.md | 1 + .../manger/demo2/PreAuthorizationManager.java | 65 +++++++++ 12 files changed, 321 insertions(+), 116 deletions(-) rename spring-security/step-3/src/main/java/com/spring/step3/security/annotation/{programmatically => }/AuthorizationLogic.java (81%) rename spring-security/step-3/src/main/java/com/spring/step3/security/{manger => config}/AuthorizationManagerConfiguration.java (59%) rename spring-security/step-3/src/main/java/com/spring/step3/security/{ => config}/properties/SecurityConfigProperties.java (93%) delete mode 100644 spring-security/step-3/src/main/java/com/spring/step3/security/manger/PreAuthorizationManager.java create mode 100644 spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/PreAuthorizationManager.java create mode 100644 spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/ReadMe.md create mode 100644 spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo2/PreAuthorizationManager.java diff --git a/spring-security/ReadMe.md b/spring-security/ReadMe.md index 80f6a75..f4cee03 100644 --- a/spring-security/ReadMe.md +++ b/spring-security/ReadMe.md @@ -1127,14 +1127,55 @@ public Result lowerUser(String name) { } ``` -### 使用自定义授权管理器 +### 使用自定义授权管理器实现自定义权限校验 在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager`。 实现完成后需要显式的在配置中禁用原先的内容。 +#### 前置条件 + +为了做一个较为通用看起来比较规范,写了一个配置类,可以在Spring配置文件中进行配置。 + +```java +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "security-path") +@Schema(name = "SecurityPathsProperties对象", description = "路径忽略和认证") +public class SecurityConfigProperties { + + @Schema(name = "noAuthPaths", description = "不用认证的路径") + public List noAuthPaths; + + @Schema(name = "securedPaths", description = "需要认证的路径") + public List securedPaths; + + @Schema(name = "允许的角色或权限", description = "允许的角色或权限") + public List adminAuthorities; + +} +``` + +之后在配置文件中指定哪些可以放行的角色或权限。 + +```yaml +security-path: + # 配置放行的角色或权限 + admin-authorities: + - "ADMIN" +``` + #### 1. 实现前置 +**方式一:正则表达式** + +> [!WARNING] +> +> 这种实现方式是要判断传入的内容是否和预期一致,这里使用了正则,但并不是很优雅。 +> +> 有些违背了Spring设计哲学。 + 在方法中写入自己的校验逻辑。 `MethodInvocation`类型是执行方法之前相当于是一个反射,可以获取到当前方法上的注解、方法名称、当前类等。 @@ -1223,6 +1264,90 @@ public class PreAuthorizationManager implements AuthorizationManager { + + private final SecurityConfigProperties securityConfigProperties; + private final MethodSecurityExpressionHandler expressionHandler; + + @Override + public AuthorizationDecision check(Supplier authenticationSupplier, MethodInvocation methodInvocation) { + + // 获取方法上的@PreAuthorize注解 + PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class); + + if (preAuthorize == null) { + // 没有注解默认放行 + return new AuthorizationDecision(true); + } + + // 使用Spring的表达式解析器 + EvaluationContext ctx = expressionHandler.createEvaluationContext(authenticationSupplier.get(), methodInvocation); + + try { + // 解析表达式并获取结果 + Expression expression = expressionHandler.getExpressionParser().parseExpression(preAuthorize.value()); + + boolean granted = Boolean.TRUE.equals(expression.getValue(ctx, Boolean.class)); + + // 如果表达式不通过,检查是否是admin + if (!granted) { + granted = isAdmin(authenticationSupplier.get()); + } + + return new AuthorizationDecision(granted); + } catch (EvaluationException | ParseException e) { + return new AuthorizationDecision(false); + } + } + + private boolean isAdmin(Authentication authentication) { + return securityConfigProperties.getAdminAuthorities().stream() + .anyMatch(auth -> authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .anyMatch(ga -> ga.equals(auth))); + } +} +``` + #### 2. 实现后置 ```java diff --git a/spring-security/step-3/src/main/java/com/spring/step3/controller/test/SecurityPreAuthorizationController.java b/spring-security/step-3/src/main/java/com/spring/step3/controller/test/SecurityPreAuthorizationController.java index f8615ed..a41a674 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/controller/test/SecurityPreAuthorizationController.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/controller/test/SecurityPreAuthorizationController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/security/pre") public class SecurityPreAuthorizationController { - @PreAuthorize("hasPermission('role::read')") + @PreAuthorize("hasAuthority('role::read')") @Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口") @GetMapping("role-user") public Result roleUser() { @@ -28,7 +28,7 @@ public class SecurityPreAuthorizationController { return Result.success(data); } - @PreAuthorize("'admin'") + @PreAuthorize("hasAnyRole('admin') || hasAuthority('USER')") @Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口") @GetMapping("lower-admin") public Result lowerAdmin() { diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/annotation/programmatically/AuthorizationLogic.java b/spring-security/step-3/src/main/java/com/spring/step3/security/annotation/AuthorizationLogic.java similarity index 81% rename from spring-security/step-3/src/main/java/com/spring/step3/security/annotation/programmatically/AuthorizationLogic.java rename to spring-security/step-3/src/main/java/com/spring/step3/security/annotation/AuthorizationLogic.java index 41b3f44..0e2af49 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/annotation/programmatically/AuthorizationLogic.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/annotation/AuthorizationLogic.java @@ -1,4 +1,4 @@ -package com.spring.step3.security.annotation.programmatically; +package com.spring.step3.security.annotation; import org.springframework.stereotype.Component; diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/AuthorizationManagerConfiguration.java b/spring-security/step-3/src/main/java/com/spring/step3/security/config/AuthorizationManagerConfiguration.java similarity index 59% rename from spring-security/step-3/src/main/java/com/spring/step3/security/manger/AuthorizationManagerConfiguration.java rename to spring-security/step-3/src/main/java/com/spring/step3/security/config/AuthorizationManagerConfiguration.java index e0dc9eb..61869ca 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/AuthorizationManagerConfiguration.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/config/AuthorizationManagerConfiguration.java @@ -1,10 +1,14 @@ -package com.spring.step3.security.manger; +package com.spring.step3.security.config; +import com.spring.step3.security.manger.demo1.PostAuthorizationManager; +import com.spring.step3.security.manger.demo2.PreAuthorizationManager; import org.springframework.aop.Advisor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -13,6 +17,16 @@ import org.springframework.security.config.annotation.method.configuration.Enabl @EnableMethodSecurity(prePostEnabled = false) public class AuthorizationManagerConfiguration { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public MethodSecurityExpressionHandler methodSecurityExpressionHandler() { + DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); + // // 可选配置---移除 ROLE_ 前缀 + // handler.setDefaultRolePrefix(""); + return handler; + } + + @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) Advisor preAuthorize(PreAuthorizationManager manager) { diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/config/SecurityWebConfiguration.java b/spring-security/step-3/src/main/java/com/spring/step3/security/config/SecurityWebConfiguration.java index 429809d..5c3237e 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/config/SecurityWebConfiguration.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/config/SecurityWebConfiguration.java @@ -1,9 +1,9 @@ package com.spring.step3.security.config; +import com.spring.step3.security.config.properties.SecurityConfigProperties; import com.spring.step3.security.filter.JwtAuthenticationFilter; import com.spring.step3.security.handler.SecurityAccessDeniedHandler; import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint; -import com.spring.step3.security.properties.SecurityConfigProperties; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/properties/SecurityConfigProperties.java b/spring-security/step-3/src/main/java/com/spring/step3/security/config/properties/SecurityConfigProperties.java similarity index 93% rename from spring-security/step-3/src/main/java/com/spring/step3/security/properties/SecurityConfigProperties.java rename to spring-security/step-3/src/main/java/com/spring/step3/security/config/properties/SecurityConfigProperties.java index 75c42a8..2d57e7e 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/properties/SecurityConfigProperties.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/config/properties/SecurityConfigProperties.java @@ -1,4 +1,4 @@ -package com.spring.step3.security.properties; +package com.spring.step3.security.config.properties; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/filter/JwtAuthenticationFilter.java b/spring-security/step-3/src/main/java/com/spring/step3/security/filter/JwtAuthenticationFilter.java index 8616aac..a842af8 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/filter/JwtAuthenticationFilter.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/filter/JwtAuthenticationFilter.java @@ -4,8 +4,8 @@ import com.spring.step3.config.context.BaseContext; import com.spring.step3.domain.vo.result.ResultCodeEnum; import com.spring.step3.exception.AuthenticSecurityException; import com.spring.step3.exception.MyAuthenticationException; +import com.spring.step3.security.config.properties.SecurityConfigProperties; import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint; -import com.spring.step3.security.properties.SecurityConfigProperties; import com.spring.step3.security.service.DbUserDetailService; import com.spring.step3.security.service.JwtTokenService; import jakarta.servlet.FilterChain; diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PostAuthorizationManager.java b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PostAuthorizationManager.java index e10cff3..d585a5a 100644 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PostAuthorizationManager.java +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PostAuthorizationManager.java @@ -1,7 +1,7 @@ -package com.spring.step3.security.manger; +package com.spring.step3.security.manger.demo1; import com.spring.step3.domain.vo.result.Result; -import com.spring.step3.security.properties.SecurityConfigProperties; +import com.spring.step3.security.config.properties.SecurityConfigProperties; import lombok.RequiredArgsConstructor; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PreAuthorizationManager.java b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PreAuthorizationManager.java deleted file mode 100644 index ea31bbe..0000000 --- a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/PreAuthorizationManager.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.spring.step3.security.manger; - -import com.spring.step3.security.properties.SecurityConfigProperties; -import lombok.RequiredArgsConstructor; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 处理方法调用前的授权检查 - * check()方法接收的是MethodInvocation对象,包含即将执行的方法调用信息 - * 用于决定是否允许执行某个方法 - * 这是传统的"前置授权"模式 - */ -@Component -@RequiredArgsConstructor -public class PreAuthorizationManager implements AuthorizationManager { - - private final SecurityConfigProperties securityConfigProperties; - - @Override - public AuthorizationDecision check(Supplier authenticationSupplier, MethodInvocation methodInvocation) { - Authentication authentication = authenticationSupplier.get(); - - // 如果方法有 @PreAuthorize 注解,会先到这里 - if (authentication == null || !authentication.isAuthenticated()) { - return new AuthorizationDecision(false); - } - - // 检查权限 - boolean granted = hasPermission(authentication, methodInvocation); - return new AuthorizationDecision(granted); - } - - private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) { - PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class); - if (preAuthorize == null) { - return true; // 没有注解默认放行 - } - - String expression = preAuthorize.value(); - // 解析表达式中的权限要求 - List requiredAuthorities = extractAuthoritiesFromExpression(expression); - - // 获取配置的admin权限 - List adminAuthorities = securityConfigProperties.getAdminAuthorities(); - - return authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .anyMatch(auth -> - adminAuthorities.contains(auth) || - requiredAuthorities.contains(auth) - ); - } - - private List extractAuthoritiesFromExpression(String expression) { - List authorities = new ArrayList<>(); - - // 处理 hasAuthority('permission') 格式 - Pattern hasAuthorityPattern = Pattern.compile("hasAuthority\\('([^']+)'\\)"); - Matcher hasAuthorityMatcher = hasAuthorityPattern.matcher(expression); - while (hasAuthorityMatcher.find()) { - authorities.add(hasAuthorityMatcher.group(1)); - } - - // 处理 hasRole('ROLE_XXX') 格式 (Spring Security 会自动添加 ROLE_ 前缀) - Pattern hasRolePattern = Pattern.compile("hasRole\\('([^']+)'\\)"); - Matcher hasRoleMatcher = hasRolePattern.matcher(expression); - while (hasRoleMatcher.find()) { - authorities.add(hasRoleMatcher.group(1)); - } - - // 处理 hasAnyAuthority('perm1','perm2') 格式 - Pattern hasAnyAuthorityPattern = Pattern.compile("hasAnyAuthority\\(([^)]+)\\)"); - Matcher hasAnyAuthorityMatcher = hasAnyAuthorityPattern.matcher(expression); - while (hasAnyAuthorityMatcher.find()) { - String[] perms = hasAnyAuthorityMatcher.group(1).split(","); - for (String perm : perms) { - authorities.add(perm.trim().replaceAll("'", "")); - } - } - - // 处理 hasAnyRole('role1','role2') 格式 - Pattern hasAnyRolePattern = Pattern.compile("hasAnyRole\\(([^)]+)\\)"); - Matcher hasAnyRoleMatcher = hasAnyRolePattern.matcher(expression); - while (hasAnyRoleMatcher.find()) { - String[] roles = hasAnyRoleMatcher.group(1).split(","); - for (String role : roles) { - authorities.add(role.trim().replaceAll("'", "")); - } - } - - return authorities; - } -} \ No newline at end of file diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/PreAuthorizationManager.java b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/PreAuthorizationManager.java new file mode 100644 index 0000000..5adfbd3 --- /dev/null +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/PreAuthorizationManager.java @@ -0,0 +1,106 @@ +// package com.spring.step3.security.manger.demo1; +// +// import com.spring.step3.security.properties.SecurityConfigProperties; +// import lombok.RequiredArgsConstructor; +// import org.aopalliance.intercept.MethodInvocation; +// import org.springframework.core.annotation.AnnotationUtils; +// import org.springframework.security.access.prepost.PreAuthorize; +// import org.springframework.security.authorization.AuthorizationDecision; +// import org.springframework.security.authorization.AuthorizationManager; +// import org.springframework.security.core.Authentication; +// import org.springframework.security.core.GrantedAuthority; +// import org.springframework.stereotype.Component; +// +// import java.util.ArrayList; +// import java.util.List; +// import java.util.function.Supplier; +// import java.util.regex.Matcher; +// import java.util.regex.Pattern; +// +// /** +// * 处理方法调用前的授权检查 +// * check()方法接收的是MethodInvocation对象,包含即将执行的方法调用信息 +// * 用于决定是否允许执行某个方法 +// * 这是传统的"前置授权"模式 +// */ +// @Component +// @RequiredArgsConstructor +// public class PreAuthorizationManager implements AuthorizationManager { +// +// private final SecurityConfigProperties securityConfigProperties; +// +// @Override +// public AuthorizationDecision check(Supplier authenticationSupplier, MethodInvocation methodInvocation) { +// Authentication authentication = authenticationSupplier.get(); +// +// // 如果方法有 @PreAuthorize 注解,会先到这里 +// if (authentication == null || !authentication.isAuthenticated()) { +// return new AuthorizationDecision(false); +// } +// +// // 检查权限 +// boolean granted = hasPermission(authentication, methodInvocation); +// return new AuthorizationDecision(granted); +// } +// +// private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) { +// PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class); +// if (preAuthorize == null) { +// return true; // 没有注解默认放行 +// } +// +// String expression = preAuthorize.value(); +// // 解析表达式中的权限要求 +// List requiredAuthorities = extractAuthoritiesFromExpression(expression); +// +// // 获取配置的admin权限 +// List adminAuthorities = securityConfigProperties.getAdminAuthorities(); +// +// return authentication.getAuthorities().stream() +// .map(GrantedAuthority::getAuthority) +// .anyMatch(auth -> +// adminAuthorities.contains(auth) || +// requiredAuthorities.contains(auth) +// ); +// } +// +// private List extractAuthoritiesFromExpression(String expression) { +// List authorities = new ArrayList<>(); +// +// // 处理 hasAuthority('permission') 格式 +// Pattern hasAuthorityPattern = Pattern.compile("hasAuthority\\('([^']+)'\\)"); +// Matcher hasAuthorityMatcher = hasAuthorityPattern.matcher(expression); +// while (hasAuthorityMatcher.find()) { +// authorities.add(hasAuthorityMatcher.group(1)); +// } +// +// // 处理 hasRole('ROLE_XXX') 格式 (Spring Security 会自动添加 ROLE_ 前缀) +// Pattern hasRolePattern = Pattern.compile("hasRole\\('([^']+)'\\)"); +// Matcher hasRoleMatcher = hasRolePattern.matcher(expression); +// while (hasRoleMatcher.find()) { +// authorities.add(hasRoleMatcher.group(1)); +// } +// +// // 处理 hasAnyAuthority('perm1','perm2') 格式 +// Pattern hasAnyAuthorityPattern = Pattern.compile("hasAnyAuthority\\(([^)]+)\\)"); +// Matcher hasAnyAuthorityMatcher = hasAnyAuthorityPattern.matcher(expression); +// while (hasAnyAuthorityMatcher.find()) { +// String[] perms = hasAnyAuthorityMatcher.group(1).split(","); +// for (String perm : perms) { +// authorities.add(perm.trim().replaceAll("'", "")); +// } +// } +// +// // 处理 hasAnyRole('role1','role2') 格式 +// Pattern hasAnyRolePattern = Pattern.compile("hasAnyRole\\(([^)]+)\\)"); +// Matcher hasAnyRoleMatcher = hasAnyRolePattern.matcher(expression); +// while (hasAnyRoleMatcher.find()) { +// String[] roles = hasAnyRoleMatcher.group(1).split(","); +// for (String role : roles) { +// authorities.add(role.trim().replaceAll("'", "")); +// } +// } +// +// return authorities; +// } +// } \ No newline at end of file diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/ReadMe.md b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/ReadMe.md new file mode 100644 index 0000000..2ec3f01 --- /dev/null +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo1/ReadMe.md @@ -0,0 +1 @@ +这里是使用正则实现,看起来并不优雅,有些违反Spring的设计哲学,但是效果可以实现。 \ No newline at end of file diff --git a/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo2/PreAuthorizationManager.java b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo2/PreAuthorizationManager.java new file mode 100644 index 0000000..8db9cf0 --- /dev/null +++ b/spring-security/step-3/src/main/java/com/spring/step3/security/manger/demo2/PreAuthorizationManager.java @@ -0,0 +1,65 @@ +package com.spring.step3.security.manger.demo2; + +import com.spring.step3.security.config.properties.SecurityConfigProperties; +import lombok.RequiredArgsConstructor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.ParseException; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.function.Supplier; + +@Component +@RequiredArgsConstructor +public class PreAuthorizationManager implements AuthorizationManager { + + private final SecurityConfigProperties securityConfigProperties; + private final MethodSecurityExpressionHandler expressionHandler; + + @Override + public AuthorizationDecision check(Supplier authenticationSupplier, MethodInvocation methodInvocation) { + + // 获取方法上的@PreAuthorize注解 + PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class); + + if (preAuthorize == null) { + // 没有注解默认放行 + return new AuthorizationDecision(true); + } + + // 使用Spring的表达式解析器 + EvaluationContext ctx = expressionHandler.createEvaluationContext(authenticationSupplier.get(), methodInvocation); + + try { + // 解析表达式并获取结果 + Expression expression = expressionHandler.getExpressionParser().parseExpression(preAuthorize.value()); + + boolean granted = Boolean.TRUE.equals(expression.getValue(ctx, Boolean.class)); + + // 如果表达式不通过,检查是否是admin + if (!granted) { + granted = isAdmin(authenticationSupplier.get()); + } + + return new AuthorizationDecision(granted); + } catch (EvaluationException | ParseException e) { + return new AuthorizationDecision(false); + } + } + + private boolean isAdmin(Authentication authentication) { + return securityConfigProperties.getAdminAuthorities().stream() + .anyMatch(auth -> authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .anyMatch(ga -> ga.equals(auth))); + } +} \ No newline at end of file