SpringBoot 自定义注解实现的权限管理

科技   2024-11-01 08:12   河北  


前言

在现代软件开发中,权限管理是一项至关重要的功能。无论是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表达式,我们可以实现灵活且强大的权限管理功能。这种方法简化了权限校验



Java技术前沿
专注分享Java技术,包括但不限于 SpringBoot,SpringCloud,Docker,消息中间件等。
 最新文章