【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制
作者:mmseoamin日期:2024-02-20

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

  • Spring SpEL
    • 基本表达式
    • 类相关表达式
    • 表达式模板
    • SpEL表达式实现权限控制
      • PreAuth
      • AuthFun
      • PreAuthAspect
      • UserController
      • SpelParserUtils

        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

            /**
             * 

            * 类相关表达式: Bean引用,参数要用单引号包括 * * @PreAuth("@af.hasPermission('ADMIN, USER')") *

            * * @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 parse(Method method, Object[] args, String spelExpression, Class 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 parse(Method method, Object[] args, String spelExpression, Class 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 parse(String param, Object paramValue, String spelExpression, Class 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 parse(String param, Object paramValue,String spelExpression, Class 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 getResult(EvaluationContext context,String spelExpression, Class 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);
                }
            }