• 浅析SpringSecurity的方法级安全管控


    一、方法安全

      除了基于URL的认证与授权,开发者也可以通过注解来灵活地配置方法安全,要使用相关注解,首先要通过@EnableGlobalMethodSecurity注解开启基于注解的安全配置:

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class WebSecurityConfig {
    }

      prePostEnabled=true会解锁@PreAuthorize和@PostAuthorize两个注解:

    (1)@PreAuthorize注解会在方法执行前进行验证

    (2)@PostAuthorize注解在方法执行后进行验证

      securedEnabled=true会解锁@Secured注解

    // 创建一个MethodService进行测试:
    @Service
    public class MethodService {
    
        //表示该访问该方法需要ADMIN角色,注意:需要加前缀ROLE_
        @Secured("ROLE_ADMIN")
        public String admin(){
            return "hello admin";
        }
    
        //表示访问该方法需要ADMIN和DBA角色
        @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
        public String adminAndDba(){
            return "hello admin and dba";
        }
    
        //表示访问该方法需要ADMIN、DBA或者USER角色
        @PreAuthorize("hasAnyRole('ADMIN', 'DBA', 'USER')")
        public String adminOrDbaOrUser(){
            return "hello admin or dba or user";
        }
    }
    
    // 配置角色:
    @Configuration
    public class MyWebSecurityConfig6 extends WebSecurityConfigurerAdapter {
        @Bean
        PasswordEncoder passwordEncoder(){
            return NoOpPasswordEncoder.getInstance();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("root").password("123").roles("ADMIN", "DBA")
                    .and()
                    .withUser("admin").password("123").roles("ADMIN", "USER")
                    .and()
                    .withUser("sang").password("123").roles("USER");
        }
    }

    二、方法级注解

      默认情况下, Spring Security 并不启用方法级的安全管控。启用方法级的管控后, 可以针对不同的方法通过注解设置不同的访问条件。

      Spring Security 支持三种方法级注解,分别是:JSR-205 注解、@Secured 注解、prePostEnabled注解。这些注解不仅可以直接加在 controller 方法上,也可以注解 Service 或 DAO 类中的方法。

      启用方法级的管控代码是:新建一个 WebSecurityConfigurerAdapterConfiguration 类,加上 @EnableGlobalMethodSecurity() 注解,通过@EnableGlobalMethodSecurity 参数开启相应的方法级的管控。

    1、JSR-205 注解

    // 通过 @EnableGlobalMethodSecurity(jsr250Enabled=true), 开启 JSR-205 注解
    @DenyAll 注解, 拒绝所有的访问
    @PermitAll 注解, 运行所有访问
    @RolesAllowed({"USER","ADMIN"}), 该方法只允许有 ROLE_USER 或 ROLE_ADMIN 角色的用户访问

    2、@Secured 注解

    // 通过 @EnableGlobalMethodSecurity(securedEnabled=true), 开启 @Secured 注解
    // 只有满足角色的用户才能访问被注解的方法, 否则将会抛出 AccessDenied 异常
    // 例子:
    @Secured("ROLE_TELLER","ROLE_ADMIN"), 该方法只允许 ROLE_TELLER 或 ROLE_ADMIN 角色的用户访问
    @Secured("IS_AUTHENTICATED_ANONYMOUSLY"), 该方法允许匿名用户访问. 

    3、@PreAuthorize 类型的注解(支持 Spring 表达式)

      @EnableGlobalMethodSecurity(prePostEnabled=true), 开启 prePostEnabled 相关的注解

      JSR-205 和 @Secured 注解功能较弱, 不支持 Spring EL 表达式. 推荐使用 @PreAuthorize 类型的注解

      具体有4个注解:

    @PreAuthorize 注解, 在方法调用之前, 基于表达式结果来限制方法的使用

    @PostAuthorize 注解, 允许方法调用, 但是如果表达式结果为 false, 将抛出一个安全性异常

    @PostFilter 注解, 允许方法调用, 但必要按照表达式来过滤方法的结果

    @PreFilter 注解, 允许方法调用, 但必须在进入方法之前过来输入值

    // 例子:
    @PreAuthorize("hasRole('ADMIN')") //必须有 ROLE_ADMIN 角色
    public void addBook(Book book);
    
    //必须同时具备 ROLE_ADMIN 和 ROLE_DBA 角色
    @PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
    public void addBook(Book book);
    
    @PreAuthorize ("#book.owner == authentication.name")
    public void deleteBook(Book book);
    
    @PostAuthorize ("returnObject.owner == authentication.name")
    public Book getBook();

    4、@PreAuthorize 表达式

    (1)returnObject 保留名

      对于 @PostAuthorize 和 @PostFilter 注解, 可以在表达式中使用 returnObject 保留名, returnObject 代表着被注解方法的返回值, 我们可以使用 returnObject 保留名对注解方法的结果进行验证。比如:

    @PostAuthorize ("returnObject.owner == authentication.name")
    public Book getBook();

    (2) 表达式中的 # 号

      在表达式中, 可以使用 #argument123 的形式来代表注解方法中的参数 argument123

    @PreAuthorize ("#book.owner == authentication.name")
    public void deleteBook(Book book);
    
    // 还有一种 #argument123 的写法, 即使用 Spring Security @P注解来为方法参数起别名, 然后在 @PreAuthorize 等注解表达式中使用该别名
    不推荐这种写法, 代码可读性较差.
    @PreAuthorize("#c.name == authentication.name") public void doSomething(@P("c") Contact contact);

    5、内置表达式有:

    表达式 备注
    hasRole([role]) 如果有当前角色, 则返回 true(会自动加上 ROLE_ 前缀)
    hasAnyRole([role1, role2]) 如果有任一角色即可通过校验, 返回true,(会自动加上 ROLE_ 前缀)
    hasAuthority([authority]) 如果有指定权限, 则返回 true
    hasAnyAuthority([authority1, authority2]) 如果有任一指定权限, 则返回true
    principal  获取当前用户的 principal 主体对象 
    authentication  获取当前用户的 authentication 对象, 
    permitAll   总是返回 true, 表示全部允许
    denyAll  总是返回 false, 代表全部拒绝
    isAnonymous()  如果是匿名访问, 返回true
    isRememberMe()  如果是remember-me 自动认证, 则返回 true
    isAuthenticated()  如果不是匿名访问, 则返回true
    isFullAuthenticated()  如果不是匿名访问或remember-me认证登陆, 则返回true
    hasPermission(Object target, Object permission)  
    hasPermission(Object target, String targetType, Object permission)   
       

    三、实例代码

      Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,来判断用户对某个控制层的方法是否具有访问权限

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
     ...........................
    }

      或者是直接在继承 GlobalMethodSecurityConfiguration 的类上加上 @EnableGlobalMethodSecurity 注解即可。

    package com.enmox.emcs.security;
    
    @Configuration
    @EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true
    )
    public class RoleConfig extends GlobalMethodSecurityConfiguration {
        public static final String ROLE_PREFIX = "ROLE_";
        ......
    }
        @ApiOperation("媒资列表")
        @GetMapping("/findVideo")
        @PreAuthorize("hasRole('sys') or hasAuthority('video')")
        public PageInfo<VideoOrAudioVO> findVideo(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                                  @RequestParam(required = false, defaultValue = "10") Integer pageSize,
                                                  String type) {
            return vodService.findVideo(pageNum, pageSize,type);
        }
  • 相关阅读:
    ajax封装
    完美运动框架
    表单上传input=file
    面向对象入门
    浅谈javaScript内存
    关于使用iframe的父子页面进行简单的相互传值
    浅谈原生JavaScript的动画和特效
    rem 原理与简介
    移动 web 适配
    jsonp 简单封装
  • 原文地址:https://www.cnblogs.com/goloving/p/14891241.html
Copyright © 2020-2023  润新知