前言
在现代软件开发中,权限管理是一项至关重要的功能。无论是Web应用还是移动应用,都需要对用户的操作进行严格的权限控制,以确保系统的安全性和数据的完整性。本文将介绍一种通过自定义注解和Spring Expression Language(SpEL)实现强大权限管理的方法。
在项目中,权限校验服务通常是一个独立的组件。传统的权限管理框架如Shiro或Spring Security虽然功能强大,但有时候可能过于复杂,不适合一些项目的需求。为了简化权限校验业务,本文采用Spring的AOP(面向切面编程),利用自定义注解和SpEL表达式,实现精细的权限控制。
二、自定义注解
首先,我们需要定义一个自定义注解,用于标记需要进行权限校验的方法或类。这个注解将包含SpEL表达式,用于在运行时进行权限校验。
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permission {
/**
* SPEL表达式
*/
String value() default "";
/**
* 权限码
*/
String[] permissions() default {};
/**
* 资源类型, 默认为区域资源
*/
String resourceType() default "region";
}
三、切面
接下来,我们定义一个切面类,用于在方法执行前后进行权限校验。这个切面类将使用SpEL解析器解析注解中的表达式,并根据表达式的结果决定是否允许方法执行。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.BeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class PermissionAspect {
private final SpelExpressionParser parser = new SpelExpressionParser();
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private final ConcurrentHashMap<Method, Expression> expressionMap = new ConcurrentHashMap<>(256);
private final Map<String, Method> methodMap = new HashMap<>(16);
private final BeanFactory beanFactory;
private final PermissionUtils permissionUtils;
@Autowired
public PermissionAspect(BeanFactory beanFactory, PermissionUtils permissionUtils) {
this.beanFactory = beanFactory;
this.permissionUtils = permissionUtils;
// 用于将用户自定义的静态方法注册到EL上下文,注意方法不能重载!
Method[] methods = permissionUtils.getClass().getDeclaredMethods();
for (Method method : methods) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers)) {
methodMap.put(method.getName(), method);
}
}
}
@Around("@annotation(permission)")
public Object check(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable {
Method targetMethod = getTargetMethod(joinPoint);
Expression expression = getExpression(targetMethod, permission);
EvaluationContext evaluationContext = getEvaluationContext(joinPoint, targetMethod);
Object expressionValue = expression.getValue(evaluationContext);
if (expressionValue == null) {
throw new RuntimeException("expressionValue is null, please check your EL expression");
}
boolean hasPermission = false;
if (expressionValue instanceof Boolean) {
hasPermission = (Boolean) expressionValue;
} else if (expressionValue instanceof String) {
hasPermission = permissionUtils.hasPermission(expressionValue.toString(), permission.resourceType());
} else if (expressionValue instanceof Collection) {
hasPermission = permissionUtils.hasAllPermission((Collection<String>) expressionValue, permission.resourceType());
}
if (!hasPermission) {
throw new PermissionException();
}
return joinPoint.proceed();
}
private Method getTargetMethod(JoinPoint joinPoint) {
// 获取目标方法
return ((ProceedingJoinPoint) joinPoint).getSignature().toShortString().split("\\.")[1].replace("()", "").trim();
}
private Expression getExpression(Method method, Permission permission) {
Expression expression = expressionMap.get(method);
if (expression != null) {
return expression;
}
String value = permission.value();
return expressionMap.computeIfAbsent(method, k -> parser.parseRaw(value));
}
private EvaluationContext getEvaluationContext(JoinPoint joinPoint, Method method) {
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置方法参数名和值为SpEL变量
Object[] args = ((ProceedingJoinPoint) joinPoint).getArgs();
for (int i = 0; i < args.length; i++) {
// 这里可以使用更复杂的逻辑来获取参数名,如使用Spring的MethodParameter等
String paramName = "arg" + i;
context.setVariable(paramName, args[i]);
}
// 注册自定义的静态方法到EL上下文
methodMap.forEach((name, m) -> context.registerFunction(name, m));
return context;
} }
四、权限校验工具
权限校验工具类用于实现具体的权限校验逻辑。这个类可以包含一些静态方法,用于根据权限码和资源类型判断用户是否有权限。
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@Component
public class PermissionUtils {
// 假设这里有一个方法用于从数据库中获取用户的权限码
public static Set<String> getUserPermissions(String userId) {
// 模拟从数据库中获取用户权限码
Set<String> permissions = new HashSet<>();
permissions.add("READ_USER");
permissions.add("WRITE_USER");
return permissions;
}
public boolean hasPermission(String permissionCode, String resourceType) {
// 这里可以添加具体的权限校验逻辑
// 假设当前用户具有所有权限,仅作示例
return true;
}
public boolean hasAllPermission(Collection<String> permissionCodes, String resourceType) {
// 这里可以添加具体的权限校验逻辑
// 假设当前用户具有所有权限,仅作示例
return true;
}
}
五、配置类
最后,我们需要在配置类中启用AOP功能,并将切面类和权限校验工具类注册为Spring Bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public PermissionUtils permissionUtils() {
return new PermissionUtils();
}
// 其他Bean的配置...
}
六、使用示例
现在,我们可以在需要权限校验的方法或类上使用自定义的@Permission
注解,并指定SpEL表达式。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@Permission(value = "#userId == 'admin'")
@GetMapping("/admin")
public String admin() {
return "This is admin page.";
}
@Permission(value = "#userId in getUserPermissions(#userId)")
@GetMapping("/user")
public String user(String userId) {
return "This is user page.";
}
}
在上面的示例中,#userId
是方法参数,getUserPermissions(#userId)
是调用权限校验工具类中的静态方法。SpEL表达式会根据这些条件进行权限校验。
七、总结
通过自定义注解和SpEL表达式,我们可以实现灵活且强大的权限管理功能。这种方法简化了权限校验