• SpringSecurity Oauth2实战 - 09 自定义SpEL权限表达式


    相关文章:SpringSecurity Oauth2实战 - 08 SpEL权限表达式源码及两种权限控制方式原理

    参考文章:http://www.liuhaihua.cn/archives/596471.html

    上一讲我们分析了SpEL权限表达式的实现原理以及相关的源码,然后以debug的方式介绍了基于url的权限表达式和基于注解的权限表达式的调用流程,不管哪种方式权限表达式对应的都是 SecurityExpressionRoot 中方法。继续基于上一讲的内容研究如何自定义权限表达式。

    一个方法针对不同的入参可能会触发不同的权限。比如说,一个用户拥有查看A目录的权限,但是没有查看B目录的权限。而这两个动作都是调用的同一个Controller方法,只是根据入参来区分查看不同的目录。

    在这里插入图片描述

    默认的 hasAuthorityhasRole 表达式无法满足需求,因为它们只能判断一个硬编码的权限或者角色字符串。所以我们需要用到自定义表达式来自定义权限判断以满足需求。 我们将创建一个 canRead 的表达式。当入参为"A"时,将判断当前用户是否有查看A的权限;当入参为"B"时,将判断当前用户是否有查看B的权限。

    我们知道,在 @PreAuthorize 注解中使用的 hasAuthority、hasPermission、hasRole、hasAnyRole 等权限表达式都是由 SecurityExpressionRoot 及其子类提供的,准确来说是由 MethodSecurityExpressionRoot 类提供的,该类中的方法就是可以在 @PreAuthorize 注解中使用的SpEl权限表达式。

    而自定义权限表达式就是在已有方法上继续扩展新方法,我们可以像 MethodSecurityExpressionRoot 类一样,自定义类继承 SecurityExpressionRoot 类并实现 MethodSecurityExpressionOperations 接口,在该对自定义类中继续添加新的方法,进而实现自定义权限表达式。

    1. 自定义权限表达式类 CustomSecurityExpressionRoot

    public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    
        /**
         * MethodSecurityExpressionOperations 接口方法的属性
         */
        private Object filterObject;
        private Object returnObject;
        private Object target;
    
        /**
         * 添加一个新的方法,这个方法就是我们自定义的权限表达式
         */
        public boolean canRead(String foo) {
            if (foo.equals("A") && !this.hasAuthority("knowledgeEdit")) {
                return false;
            }
    
            if (foo.equals("B") && !this.hasAuthority("roleEdit")) {
                return false;
            }
            return true;
        }
    
        /**
         * 构造方法
         */
        public CustomSecurityExpressionRoot(Authentication authentication) {
            super(authentication);
        }
    
        /**
         * 下面的方法都是 MethodSecurityExpressionOperations 接口中的实现方法,没有更改
         */
    
        @Override
        public void setFilterObject(Object filterObject) {
            this.filterObject = filterObject;
        }
    
        @Override
        public Object getFilterObject() {
            return this.filterObject;
        }
    
        @Override
        public void setReturnObject(Object returnObject) {
            this.returnObject = returnObject;
        }
    
        @Override
        public Object getReturnObject() {
            return this.returnObject;
        }
    
        void setThis(Object target) {
            this.target = target;
        }
    
        @Override
        public Object getThis() {
            return target;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    2. 表达式处理器 CustomSecurityExpressionRootHandler

    把 CustomSecurityExpressionRoot 注入到表达式处理器

    public class CustomSecurityExpressionRootHandler extends DefaultMethodSecurityExpressionHandler {
        @Override
        protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
            CustomSecurityExpressionRoot customSecurityExpressionRoot = new CustomSecurityExpressionRoot(authentication);
    
            customSecurityExpressionRoot.setThis(invocation.getThis());
            customSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
            customSecurityExpressionRoot.setTrustResolver(getTrustResolver());
            customSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
            customSecurityExpressionRoot.setDefaultRolePrefix(getDefaultRolePrefix());
            return customSecurityExpressionRoot;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3. 资源服务配置类 ResourceServerAutoConfiguration

    资源服务配置类中添加 CustomSecurityExpressionRootHandler

    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
        /**
         * 权限表达式的自定义处理
         */
        @Autowired
        private GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration;
    
        @Autowired
        private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
       
        /**
         * 自定义权限表达式处理
         */
        @Bean
        public GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration() {
            List<MethodSecurityExpressionHandler> handlers = new ArrayList<>(1);
            handlers.add(customMethodSecurityExpressionHandler());
            globalMethodSecurityConfiguration.setMethodSecurityExpressionHandler(handlers);
            return globalMethodSecurityConfiguration;
        }
    
        @Bean
        public MethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
            CustomSecurityExpressionRootHandler expressionHandler = new CustomSecurityExpressionRootHandler();
            return expressionHandler;
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
            resources.tokenExtractor(tokenExtractor());
        }
    
        @Bean
        @Primary
        public TokenExtractor tokenExtractor() {
            CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
            return customTokenExtractor;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作
            http.authorizeRequests()
                    // permitAll()权限表达式
                    .antMatchers("/api/v1/login", "/api/v1/token").permitAll();
            // 其他请求只要认证后的用户就可以访问
            http.authorizeRequests().anyRequest().authenticated();
            http.formLogin().disable();
            http.httpBasic().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    4. 使用自定义权限表达式 DocController

    @RestController
    @RequestMapping("/api/v1")
    public class DocController {
    
        @PreAuthorize("canRead(#foo)")
        @GetMapping("/doc")
        public String getDocList(@RequestParam("foo") String foo){
            return foo;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5. 启动项目测试 foo=A

    在这里插入图片描述

    OAuth2AuthenticationProcessingFilter 过滤器拦截获取用户认证信息这块就不分析,前面已经分析很多遍了。

    ① 请求进入到自定义权限表达式类 CustomSecurityExpressionRoot的canRead方法 :

    在这里插入图片描述

    ② 调用父类SecurityExpressionRoot的hasAuthority方法,该方法会继续调用父类的SecurityExpressionRoot的hasAnyAuthority方法:

    在这里插入图片描述

    ③ 在hasAnyAuthority方法中调用 hasAnyAuthorityName 方法判断登录用户是否具备knowledgeEdit权限:

    在这里插入图片描述

    ④ roleSet是用户具备的所有权限,可以看到当前登录用户具有knowledgeEdit权限,因此返回true:

    在这里插入图片描述

    ⑤ 回到 CustomSecurityExpressionRoot 类的canRead方法,返回true

    在这里插入图片描述

    ⑥ 请求进入 DocController的 getDocList方法:

    在这里插入图片描述

    6. 启动项目测试 foo=B

    在这里插入图片描述

    ① 请求进入到自定义权限表达式类 CustomSecurityExpressionRoot的canRead方法 :

    在这里插入图片描述

    ② 调用父类SecurityExpressionRoot的hasAuthority方法,该方法会继续调用父类的SecurityExpressionRoot的hasAnyAuthority方法:

    在这里插入图片描述

    ③ 在hasAnyAuthority方法中调用 hasAnyAuthorityName 方法判断登录用户是否具备roleEdit权限,roleSet是用户具备的所有权限,可以看到当前登录用户不具有roleEdit权限,因此返回false:

    在这里插入图片描述

    ④ 回到 CustomSecurityExpressionRoot 类的canRead方法,返回false

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    自学WEB后端03-Node.js 语法
    关于 在国产麒麟系统上使用QProcess配合管道命令执行shell命令获取预期结果输出失败 的解决方法
    供应链安全体系建设方案
    C++ 11 知识积累
    wm命令详解
    激光slam学习
    rh358 004 bind反向,转发,主从,各种资源记录 unbound ansible部署bind unbound
    java程序设计项目案例化教程题库及答案
    feign远程调用时如何在请求头加入数据
    (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/127784660