【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制

【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制

Spring SpEL

Spring 表达式语言 SpEL 是一种非常强大的表达式语言,它支持在运行时查询和操作对象图。 它提供了许多高级功能,例如方法调用和基本的字符串模板功能。表达式语言给静态Java语言增加了动态功能。

Spring 表达式语言最初是为 Spring 社区创建的,它拥有一种受良好支持的表达式语言,可用于 Spring 产品组合中的所有产品。 虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它不直接与 Spring 绑定,可以独立使用

Spring Security框架中启用prePost注解的支持就是使用SpEL表达式实现的权限控制

  • @PreAuthorize(“hasAuthority(‘save’)”)
  • @PreAuthorize(“isAnonymous()”)
  • @PreAuthorize(“isAuthenticated()”)

SpEL支持如下表达式:

  • 基本表达式: 字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算及Elivis表达式、正则表达式、括号优先级表达式
  • 类相关表达式: 类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用
  • 集合相关表达式:内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义
  • 其他表达式:模板表达式。

注:SpEL表达式中的关键字是不区分大小写的。

基本表达式

/**
* 基本表达式 : 字面量表达式
*/
@Test
public void test01() {
    // 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
    ExpressionParser parser = new SpelExpressionParser();
    // 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
    Expression expression = parser.parseExpression("1+2");
    // 3)求值:通过Expression接口的getValue方法根据上下文获得表达式值。
    System.out.println(expression.getValue());



    String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
    int int1 = parser.parseExpression("1").getValue(Integer.class);
    long long1 = parser.parseExpression("-1L").getValue(long.class);
    float float1 = parser.parseExpression("1.1").getValue(Float.class);
    double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
    int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
    long hex2 = parser.parseExpression("0xaL").getValue(long.class);
    boolean true1 = parser.parseExpression("true").getValue(boolean.class);
    boolean false1 = parser.parseExpression("false").getValue(boolean.class);
    Object null1 = parser.parseExpression("null").getValue(Object.class);

    System.out.println("str1=" + str1);
    System.out.println("int1=" + int1);
    System.out.println("long1=" + long1);
    System.out.println("float1=" + float1);
    System.out.println("double1=" + double1);
    System.out.println("hex1=" + hex1);
    System.out.println("hex2=" + hex2);
    System.out.println("true1=" + true1);
    System.out.println("false1=" + false1);
    System.out.println("null1=" + null1);
}
/**
* 基本表达式 : 字面量表达式
*/
@Test
public void test02() {
    // 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
    ExpressionParser parser = new SpelExpressionParser();
    // 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
    Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");
    // 3)构造上下文:准备比如变量定义等等表达式需要的上下文数据。
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("end", "!");
    // 4)求值:通过Expression接口的getValue方法根据上下文获得表达式值。
    System.out.println(expression.getValue(context));
}

类相关表达式

@Data
@Component("ss")
public class User {
    private String username;
    private String address;
    private Integer age;


    public String sayHello(String username) {
        return "hello " + username;
    }

    public String sayHello(Integer age) {
        return "hello " + username + ";age=" + age;
    }
    public String sayHello() {
        return "hello " + username;
    }
}

/**
* 类相关表达式: 变量定义及引用
*/
@Test
public void test03() {
    String expression = "#user.username";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    User user = new User();
    user.setAddress("长沙");
    user.setUsername("zysheep");
    user.setAge(24);;

    ctx.setVariable("user", user);

    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}
@Test
public void test04() {
    String expression = "username";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    User user = new User();
    user.setAddress("长沙");
    user.setUsername("zysheep");
    user.setAge(24);

    // user 对象设置为 rootObject,那么表达式中就不需要 #user.
    ctx.setRootObject(user);

    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}
/**
* 类相关表达式: 对象方法调用
*/
@Test
public void test05() {
    String expression = "sayHello(99)";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);

    StandardEvaluationContext ctx = new StandardEvaluationContext();

    User user = new User();
    user.setAddress("长沙");
    user.setUsername("zysheep");
    user.setAge(24);
    ctx.setRootObject(user);

    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}
/**
* 类相关表达式: 对象方法调用
*
* 调用无参的 sayHello
*/
@Test
public void test06() {
    String expression = "sayHello()";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);

    StandardEvaluationContext ctx = new StandardEvaluationContext();

    User user = new User();
    user.setAddress("长沙");
    user.setUsername("zysheep");
    user.setAge(24);
    ctx.setRootObject(user);

    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}
/**
* 类相关表达式: Bean引用
*/
@Test
public void test07() {
    // 通过 SpEL 表达式来调用这个名为 ss 的 bean 中的 sayHello 方法
    String expression = "@ss.sayHello('spel bean引用')";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    // 给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法
    ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}

表达式模板

/**
* 表达式模板:
* 模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由“前缀+表达式+后缀”形式组成,如“${1+2}”即表达式块
*
*/
@Test
public void test08() {
    //创建解析器
    SpelExpressionParser parser = new SpelExpressionParser();
    //创建解析器上下文
    ParserContext context = new TemplateParserContext("%{", "}");
    Expression expression = parser.parseExpression("你好:%{#name},我们正在学习:%{#lesson}", context);
    //创建表达式计算上下文
    EvaluationContext evaluationContext = new StandardEvaluationContext();
    evaluationContext.setVariable("name", "zysheep");
    evaluationContext.setVariable("lesson", "SpEL表达式!");
    //获取值
    String value = expression.getValue(evaluationContext, String.class);
    System.out.println(value);
}

SpEL表达式实现权限控制

PreAuth

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {

    /**
     *
     *
     * permissionAll()-----只要配置了角色就可以访问
     * hasPermission("MENU.QUERY")-----有MENU.QUERY操作权限的角色可以访问
     * permitAll()-----放行所有请求
     * denyAll()-----只有超级管理员角色才可访问
     * hasAuth()-----只有登录后才可访问
     * hasTimeAuth(1,,10)-----只有在1-10点间访问
     * hasRole(‘管理员’)-----具有管理员角色的人才能访问
     * hasAllRole(‘管理员’,'总工程师')-----同时具有管理员、总工程师角色的人才能访问
     *
     * Spring el
     * 文档地址:https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions
     */
    String value();

}

AuthFun

/**
 * <p>
 * 类相关表达式: Bean引用,参数要用单引号包括
 *
 * @PreAuth("@af.hasPermission('ADMIN, USER')")
 * </p>
 *
 * @author : lyw
 * @since : 2023/11/23 16:01
 */
@Component("af")
public class AuthFun {


    /**
     * 判断角色是否具有接口权限
     *
     * @return {boolean}
     */
    public boolean permissionAll() {
        //TODO  读取数据库权限数据
        return true;
    }

    /**
     * 判断角色是否具有接口权限
     *
     * @param permission 权限编号,对应菜单的MENU_CODE
     * @return {boolean}
     */
    public boolean hasPermission(String permission) {
      return hasRole(permission);
    }

    /**
     * 放行所有请求
     *
     * @return {boolean}
     */
    public boolean permitAll() {
        return true;
    }

    /**
     * 只有超管角色才可访问
     *
     * @return {boolean}
     */
    public boolean denyAll() {
        return hasRole("ADMIN");
    }

    /**
     * 是否已授权
     *
     * @return {boolean}
     */
    public boolean hasAuth() {

        return true;
    }

    /**
     * 是否有时间授权
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return {boolean}
     */
    public boolean hasTimeAuth(Integer start, Integer end) {
        Integer hour = DateUtil.hour(new Date(), true);
        return hour >= start && hour <= end;
    }

    /**
     * 判断是否有该角色权限
     *
     * @param role 单角色
     * @return {boolean}
     */
    public boolean hasRole(String role) {
        return hasAnyRole(role);
    }

    /**
     * 判断是否具有所有角色权限
     *
     * @param role 角色集合
     * @return {boolean}
     */
    public boolean hasAllRole(String... role) {
        for (String r : role) {
            if (!hasRole(r)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断是否有该角色权限
     *
     * @param role 角色集合
     * @return {boolean}
     */
    public boolean hasAnyRole(String... role) {
        return Arrays.stream(role).anyMatch(item -> StringUtils.equals("ADMIN", item));
    }
}

PreAuthAspect

@Aspect
@Component
public class PreAuthAspect {

    @Autowired
    private BeanFactory applicationContext;

    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();


    @Pointcut("@annotation(cn.zysheep.annotation.PreAuth) || @within(cn.zysheep.annotation.PreAuth)")
    public void pointcut() {}


    @Around("pointcut()")
    public Object preAuth(ProceedingJoinPoint point) throws Throwable {
        if (handleAuth(point)) {
            return point.proceed();
        }
        throw new BizException("用户无权限,非法用户!");
    }


    private boolean handleAuth(ProceedingJoinPoint point) {
        MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;
        Method method = ms.getMethod();
        // 读取权限注解,优先方法上,没有则读取类
        PreAuth preAuth = method.getAnnotation( PreAuth.class);
        // 判断表达式
        String condition = preAuth.value();
        if (StringUtils.isNotBlank(condition)) {
            // @PreAuth("@af.hasPermission('ADMIN, USER')")
            Expression expression = EXPRESSION_PARSER.parseExpression(condition);
            // 方法参数值
            Object[] args = point.getArgs();
            StandardEvaluationContext context = getEvaluationContext(method, args);
            // 获取解析计算的结果
            return expression.getValue(context, Boolean.class);
        }
        return false;
    }
    /**
     * 获取方法上的参数
     *
     * @param method 方法
     * @param args   变量
     * @return {SimpleEvaluationContext}
     */
    private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
        // 初始化Spel表达式上下文,并设置 AuthFun
        StandardEvaluationContext context = new StandardEvaluationContext();
        // 设置表达式支持spring bean
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));

        // 可以从session中获取登录用户的权限
//        for (int i = 0; i < args.length; i++) {
//            // 读取方法参数
//            MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
//            // 设置方法 参数名和值 为spel变量
//            context.setVariable(methodParam.getParameterName(), args[i]);
//        }
        return context;
    }
}

UserController

@RestController
@RequestMapping("/api")
@Slf4j
public class UserController {


    @GetMapping("/save")
    @PreAuth("@af.hasPermission('ADMIN')")
    public R save() {
        log.info("====执行保存业务逻辑=====");
        return R.success();
    }

    @GetMapping("/get")
    @PreAuth("@af.hasPermission('USER')")
    public R get() {
        log.info("====执行保存业务逻辑=====");
        return R.success();
    }

}

SpelParserUtils

public final class SpelParserUtils {

    private static final String EXPRESSION_PREFIX = "#{";

    private static final String EXPRESSION_SUFFIX = "}";

    /**
     * 表达式解析器
     */
    private static ExpressionParser expressionParser = new SpelExpressionParser();

    /**
     *  参数名解析器,用于获取参数名
     */
    private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();



    private SpelParserUtils(){}

    /**
     * 解析spel表达式
     *
     * @param method 方法
     * @param args 参数值
     * @param spelExpression  表达式
     * @param clz  返回结果的类型
     * @param defaultResult 默认结果
     * @return 执行spel表达式后的结果
     */
    public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz, T defaultResult) {
        String[] params = parameterNameDiscoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        //设置上下文变量
        for (int i = 0; i < params.length; i++) {
            context.setVariable(params[i], args[i]);
        }
        T result = getResult(context,spelExpression,clz);
        if(Objects.isNull(result)){
            return defaultResult;
        }
        return result;
    }

    /**
     * 解析spel表达式
     *
     * @param method  方法
     * @param args 参数值
     * @param spelExpression  表达式
     * @param clz  返回结果的类型
     * @return 执行spel表达式后的结果
     */
    public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz) {
        String[] params = parameterNameDiscoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        //设置上下文变量
        for (int i = 0; i < params.length; i++) {
            context.setVariable(params[i], args[i]);
        }
        return getResult(context,spelExpression,clz);
    }

    /**
     * 解析spel表达式
     *
     * @param param  参数名
     * @param paramValue 参数值
     * @param spelExpression  表达式
     * @param clz  返回结果的类型
     * @return 执行spel表达式后的结果
     */
    public static <T> T parse(String param, Object paramValue, String spelExpression, Class<T> clz) {
        EvaluationContext context = new StandardEvaluationContext();
        //设置上下文变量
        context.setVariable(param, paramValue);
        return getResult(context,spelExpression,clz);
    }


    /**
     * 解析spel表达式
     *
     * @param param 参数名
     * @param paramValue 参数值
     * @param spelExpression  表达式
     * @param clz  返回结果的类型
     * @param defaultResult 默认结果
     * @return 执行spel表达式后的结果
     */
    public static <T> T parse(String param, Object paramValue,String spelExpression, Class<T> clz, T defaultResult) {
        EvaluationContext context = new StandardEvaluationContext();
        //设置上下文变量
        context.setVariable(param, paramValue);
        T result = getResult(context,spelExpression,clz);
        if(Objects.isNull(result)){
            return defaultResult;
        }
        return result;

    }


    /**
     * 获取spel表达式后的结果
     *
     * @param context 解析器上下文接口
     * @param spelExpression  表达式
     * @param clz  返回结果的类型
     * @return 执行spel表达式后的结果
     */
    private static <T> T getResult(EvaluationContext context,String spelExpression, Class<T> clz){
        try {
            //解析表达式
            Expression expression = parseExpression(spelExpression);
            //获取表达式的值
            return expression.getValue(context, clz);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
        return null;
    }


    /**
     * 解析表达式
     * @param spelExpression spel表达式
     * @return
     */
    private static Expression parseExpression(String spelExpression){
        // 如果表达式是一个#{}表达式,需要为解析传入模板解析器上下文
        if(spelExpression.startsWith(EXPRESSION_PREFIX) && spelExpression.endsWith(EXPRESSION_SUFFIX)){
            return expressionParser.parseExpression(spelExpression,new TemplateParserContext());
        }

        return expressionParser.parseExpression(spelExpression);
    }

}
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Spring Boot中使用AOP自定义权限注解可以通过以下步骤实现: 1. 首先,在pom.xml文件中添加Spring Boot AOP的依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` \[1\] 2. 创建一个自定义的注解,用于标记需要进行权限控制的方法或类。例如,可以创建一个名为@CustomPermission的注解。 3. 创建一个切面类,用于定义权限控制的逻辑。在切面类中,可以使用@Before、@After、@Around等注解来定义在方法执行前、执行后或者环绕方法执行时需要执行的逻辑。在这个切面类中,可以通过获取方法的参数、注解等信息来进行权限校验和控制。 4. 在Spring Boot的配置类中,使用@EnableAspectJAutoProxy注解来启用AOP功能。 5. 在需要进行权限控制的方法或类上,添加自定义的权限注解@CustomPermission。 通过以上步骤,就可以在Spring Boot中使用AOP自定义权限注解实现权限控制了。使用AOP可以更加灵活地对方法进行拦截和处理,同时可以通过自定义注解来标记需要进行权限控制的方法或类。\[2\]\[3\] #### 引用[.reference_title] - *1* [springboot+mybatis+aop+注解实现数据权限](https://blog.csdn.net/weixin_42935902/article/details/116758260)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [springboot+自定义注解+AOP实现权限控制(一)](https://blog.csdn.net/byteArr/article/details/103984725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李熠漾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值