首页 > 解决方案 > Spring Boot:配置自定义 MethodSecurityExpressionOperations?

问题描述

我在解决我曾经使用过的 Spring Boot 安全配置时遇到了问题,但现在无法识别我的自定义定义。我的目标是使用自定义注释在 Spring 中使用方法级别的安全性来保护我们所有的服务。

当我启动服务时,我的 CustomMethodSecurityConfig 被实例化并调用 createExpressionHandler(),但是当我向服务发出请求时,它不会在我的 CustomMethodSecurityExpressionHandler 上调用 createSecurityExpressionRoot(...),而是在 DefaultWebSecurityExpressionHandler 上调用。

我很感激任何人都可以提供关于为什么 Spring Security 无法识别我在 CustomMethodSecurityExpressionRoot 中定义的表达式的任何见解。

这是我的 GlobalMethodSecurityConfiguration 类的片段

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration {

  private final MyService1 myService1;
  private final MyService2 myService2;
  private final MyService3 myService3;

  @Autowired
  public CustomMethodSecurityConfig(MyService1 myService1, MyService2 myService2,
                                    MyService3 myService3) {
    this.myService1 = myService1;
    this.myService2 = myService2;
    this.myService3 = myService3;
  }

  @Override
  protected MethodSecurityExpressionHandler createExpressionHandler() {
    CustomMethodSecurityExpressionHandler expressionHandler =
        new CustomMethodSecurityExpressionHandler(myService1, myService2, myService3);
    expressionHandler.setPermissionEvaluator(permissionEvaluator());
    return expressionHandler;
  }
}

这是我的 DefaultMethodSecurityExpressionHandler 类的片段

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

  private final MyService1 myService1;
  private final MyService2 myService2;
  private final MyService3 myService3;
  private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

  public CustomMethodSecurityExpressionHandler(MyService1 myService1, MyService2 myService2,
                                               MyService3 myService3) {
    this.myService1 = myService1;
    this.myService2 = myService2;
    this.myService3 = myService3;
  }

  @Override
  protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
                                                                            MethodInvocation invocation) {
    CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication,
                                                                                     myService1,
                                                                                     myService2,
                                                                                     myService3);

    root.setPermissionEvaluator(getPermissionEvaluator());
    root.setTrustResolver(this.trustResolver);
    root.setRoleHierarchy(getRoleHierarchy());

    return root;
  }
}

这是我的 SecurityExpressionRoot 的片段,这是我定义我在服务注释中使用的 SpEL 表达式的地方。我只包含了一个简化的 isUser 作为示例。这些方法的作用并不重要,重要的是它们是可见的。

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot
    implements MethodSecurityExpressionOperations {

  private Object filterObject;
  private Object returnObject;

  private MyService1 myService1;
  private MyService2 myService2;
  private MyService3 myService3;

  public CustomMethodSecurityExpressionRoot(
      Authentication authentication,
      MyService1 myService1,
      MyService2 myService2,
      MyService3 myService3) {
    super(authentication);
    this.myService1 = myService1;
    this.myService2 = myService2;
    this.myService3 = myService3;
  }

  @Override
  public Object getFilterObject() {
    return this.filterObject;
  }

  @Override
  public Object getReturnObject() {
    return this.returnObject;
  }

  @Override
  public void setFilterObject(Object obj) {
    this.filterObject = obj;
  }

  @Override
  public void setReturnObject(Object obj) {
    this.returnObject = obj;
  }

  @Override
  public Object getThis() {
    return this;
  }

  //All custom SpEL methods
  public boolean isUser(Long userId) {
    SecurityUser user = (SecurityUser) this.getPrincipal();
    return user.getUserId() == userId;
  }

  ...

}

最后,这是我的 WebSecurityConfigurerAdapter 的片段,它被串联使用,它验证来自我们的 UAA 服务器的外部身份验证令牌。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
    prePostEnabled = true,
    proxyTargetClass = true)
public class ServiceSecurityConfig extends WebSecurityConfigurerAdapter {

  private final TokenCheckService _tokenCheckService;

  @Autowired
  ServiceSecurityConfig(TokenCheckService tokenCheckService) {
    _tokenCheckService = tokenCheckService;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(new TokenAuthenticationProvider(_tokenCheckService));
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers(HttpMethod.OPTIONS, "/api/**");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
        http
            .anonymous()
              .disable()
            .csrf()
              .disable()
            .exceptionHandling()
              .authenticationEntryPoint(new UnAuthorizedEntryPoint())
              .and()
            .sessionManagement()
              .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and()
            .authorizeRequests()
              .anyRequest().authenticated();
    http.addFilterBefore(new AuthenticationTokenFilter(), BasicAuthenticationFilter.class);
  }
}

编辑:我似乎认为这是我的 WebDecisionVoters 在初始化期间被覆盖的问题。如果我在肯定构造函数中有一个断点

AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters)

我可以看到 AffirmativeBased 被 3 个决策投票者实例化,其中一个是 PreInvocationAuthorizationAdviceVoter,其中包含对我的表达式处理程序的引用。我相信这是由 methodSecurityInterceptor 的 bean 实例化创建的。

当我继续断点时,我再次点击了相同的基于 Affirmative 的构造函数,但只有一个决策投票者,即一个引用 DefaultWebSecurityExpressionHandler 实例的 WebExperssionVoter。我相信这是由 springSecurityFilterChain 的 bean 实例化创建的。

标签: javaspringspring-bootspring-securityconfiguration

解决方案


我可以按照Custom SecurityExpression with Service中的步骤解决此问题。问题似乎与我的与安全性分开的自动接线服务有关。导致问题的 MyService1、MyService2 和 MyService3 并删除它们允许安全工作。

任何附加服务都必须在扩展 DefaultMethodSecurityExpressionHandler 的类的 createSecurityExpressionRoot 中设置。

@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
    CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
    // Other initialization
    root.setMyService1(applicationContext.getBean(MyService1.class));
    root.setMyService2(applicationContext.getBean(MyService2.class));
    root.setMyService3(applicationContext.getBean(MyService3.class));
    return root;
}

推荐阅读