文章目录

  • 1. 自定义权限表达式类 CustomSecurityExpressionRoot
  • 2. 表达式处理器 CustomSecurityExpressionRootHandler
  • 3. 资源服务配置类 ResourceServerAutoConfiguration
  • 4. 使用自定义权限表达式 DocController
  • 5. 启动项目测试 foo=A
  • 6. 启动项目测试 foo=B

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

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

spring securety 保护actuator spring security spel_linux

默认的 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;
    }
}

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;
    }
}

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();
    }
}

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

@RestController
@RequestMapping("/api/v1")
public class DocController {

    @PreAuthorize("canRead(#foo)")
    @GetMapping("/doc")
    public String getDocList(@RequestParam("foo") String foo){
        return foo;
    }
}

5. 启动项目测试 foo=A

spring securety 保护actuator spring security spel_java_02

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

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

spring securety 保护actuator spring security spel_前端_03

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

spring securety 保护actuator spring security spel_自定义权限_04

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

spring securety 保护actuator spring security spel_linux_05

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

spring securety 保护actuator spring security spel_ide_06

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

spring securety 保护actuator spring security spel_linux_07

⑥ 请求进入 DocController的 getDocList方法:

spring securety 保护actuator spring security spel_java_08

6. 启动项目测试 foo=B

spring securety 保护actuator spring security spel_linux_09

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

spring securety 保护actuator spring security spel_ide_10

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

spring securety 保护actuator spring security spel_java_11

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

spring securety 保护actuator spring security spel_ide_12

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

spring securety 保护actuator spring security spel_linux_13

spring securety 保护actuator spring security spel_前端_14