使用自带的校验方法,完成权限表达式解析

This commit is contained in:
bunny 2025-07-19 23:49:01 +08:00
parent d8a34d7fa6
commit 1940b29247
12 changed files with 321 additions and 116 deletions

View File

@ -1127,14 +1127,55 @@ public Result<String> lowerUser(String name) {
} }
``` ```
### 使用自定义授权管理器 ### 使用自定义授权管理器实现自定义权限校验
在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager<T>`。 在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager<T>`。
实现完成后需要显式的在配置中禁用原先的内容。 实现完成后需要显式的在配置中禁用原先的内容。
#### 前置条件
为了做一个较为通用看起来比较规范写了一个配置类可以在Spring配置文件中进行配置。
```java
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "security-path")
@Schema(name = "SecurityPathsProperties对象", description = "路径忽略和认证")
public class SecurityConfigProperties {
@Schema(name = "noAuthPaths", description = "不用认证的路径")
public List<String> noAuthPaths;
@Schema(name = "securedPaths", description = "需要认证的路径")
public List<String> securedPaths;
@Schema(name = "允许的角色或权限", description = "允许的角色或权限")
public List<String> adminAuthorities;
}
```
之后在配置文件中指定哪些可以放行的角色或权限。
```yaml
security-path:
# 配置放行的角色或权限
admin-authorities:
- "ADMIN"
```
#### 1. 实现前置 #### 1. 实现前置
**方式一:正则表达式**
> [!WARNING]
>
> 这种实现方式是要判断传入的内容是否和预期一致,这里使用了正则,但并不是很优雅。
>
> 有些违背了Spring设计哲学。
在方法中写入自己的校验逻辑。 在方法中写入自己的校验逻辑。
`MethodInvocation`类型是执行方法之前相当于是一个反射,可以获取到当前方法上的注解、方法名称、当前类等。 `MethodInvocation`类型是执行方法之前相当于是一个反射,可以获取到当前方法上的注解、方法名称、当前类等。
@ -1223,6 +1264,90 @@ public class PreAuthorizationManager implements AuthorizationManager<MethodInvoc
} }
``` ```
**方式二:自带的**
需要重新设置配置:
```java
@Configuration
@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) {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorize(PostAuthorizationManager manager) {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
}
}
```
实现
```java
@Component
@RequiredArgsConstructor
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final SecurityConfigProperties securityConfigProperties;
private final MethodSecurityExpressionHandler expressionHandler;
@Override
public AuthorizationDecision check(Supplier<Authentication> 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. 实现后置 #### 2. 实现后置
```java ```java

View File

@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/security/pre") @RequestMapping("/api/security/pre")
public class SecurityPreAuthorizationController { public class SecurityPreAuthorizationController {
@PreAuthorize("hasPermission('role::read')") @PreAuthorize("hasAuthority('role::read')")
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口") @Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
@GetMapping("role-user") @GetMapping("role-user")
public Result<String> roleUser() { public Result<String> roleUser() {
@ -28,7 +28,7 @@ public class SecurityPreAuthorizationController {
return Result.success(data); return Result.success(data);
} }
@PreAuthorize("'admin'") @PreAuthorize("hasAnyRole('admin') || hasAuthority('USER')")
@Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口") @Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口")
@GetMapping("lower-admin") @GetMapping("lower-admin")
public Result<String> lowerAdmin() { public Result<String> lowerAdmin() {

View File

@ -1,4 +1,4 @@
package com.spring.step3.security.annotation.programmatically; package com.spring.step3.security.annotation;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -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.aop.Advisor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
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.context.annotation.Role; 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.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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) @EnableMethodSecurity(prePostEnabled = false)
public class AuthorizationManagerConfiguration { public class AuthorizationManagerConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
// // 可选配置---移除 ROLE_ 前缀
// handler.setDefaultRolePrefix("");
return handler;
}
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize(PreAuthorizationManager manager) { Advisor preAuthorize(PreAuthorizationManager manager) {

View File

@ -1,9 +1,9 @@
package com.spring.step3.security.config; 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.filter.JwtAuthenticationFilter;
import com.spring.step3.security.handler.SecurityAccessDeniedHandler; import com.spring.step3.security.handler.SecurityAccessDeniedHandler;
import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint; import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint;
import com.spring.step3.security.properties.SecurityConfigProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter; import lombok.Getter;

View File

@ -4,8 +4,8 @@ import com.spring.step3.config.context.BaseContext;
import com.spring.step3.domain.vo.result.ResultCodeEnum; import com.spring.step3.domain.vo.result.ResultCodeEnum;
import com.spring.step3.exception.AuthenticSecurityException; import com.spring.step3.exception.AuthenticSecurityException;
import com.spring.step3.exception.MyAuthenticationException; 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.handler.SecurityAuthenticationEntryPoint;
import com.spring.step3.security.properties.SecurityConfigProperties;
import com.spring.step3.security.service.DbUserDetailService; import com.spring.step3.security.service.DbUserDetailService;
import com.spring.step3.security.service.JwtTokenService; import com.spring.step3.security.service.JwtTokenService;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;

View File

@ -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.domain.vo.result.Result;
import com.spring.step3.security.properties.SecurityConfigProperties; import com.spring.step3.security.config.properties.SecurityConfigProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManager;

View File

@ -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<MethodInvocation> {
private final SecurityConfigProperties securityConfigProperties;
@Override
public AuthorizationDecision check(Supplier<Authentication> 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<String> requiredAuthorities = extractAuthoritiesFromExpression(expression);
// 获取配置的admin权限
List<String> adminAuthorities = securityConfigProperties.getAdminAuthorities();
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(auth ->
adminAuthorities.contains(auth) ||
requiredAuthorities.contains(auth)
);
}
private List<String> extractAuthoritiesFromExpression(String expression) {
List<String> 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;
}
}

View File

@ -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<MethodInvocation> {
//
// private final SecurityConfigProperties securityConfigProperties;
//
// @Override
// public AuthorizationDecision check(Supplier<Authentication> 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<String> requiredAuthorities = extractAuthoritiesFromExpression(expression);
//
// // 获取配置的admin权限
// List<String> adminAuthorities = securityConfigProperties.getAdminAuthorities();
//
// return authentication.getAuthorities().stream()
// .map(GrantedAuthority::getAuthority)
// .anyMatch(auth ->
// adminAuthorities.contains(auth) ||
// requiredAuthorities.contains(auth)
// );
// }
//
// private List<String> extractAuthoritiesFromExpression(String expression) {
// List<String> 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;
// }
// }

View File

@ -0,0 +1 @@
这里是使用正则实现看起来并不优雅有些违反Spring的设计哲学但是效果可以实现。

View File

@ -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<MethodInvocation> {
private final SecurityConfigProperties securityConfigProperties;
private final MethodSecurityExpressionHandler expressionHandler;
@Override
public AuthorizationDecision check(Supplier<Authentication> 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)));
}
}