You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security资源服务器配置accessDecisionManager遇SpEL表达式错误

问题

在为资源服务器的HttpSecurity配置关联AccessDecisionManager并使用OAuth2 SpEL表达式时遇到异常。已按照文档及相关指导实现,但始终报错。当前使用版本:spring-security-oauth2-2.3.6、spring-security-core-5.6.2。

资源服务器配置代码

@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private AccessDeniedHandler oauthAccessDeniedHandler;

    @Autowired
    private ResourceServerTokenServices tokenServices;

    @Override
    public void configure(HttpSecurity http) throws Exception {
    
        http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .csrf().disable()
            .anonymous().disable()
            .authorizeRequests()
            .antMatchers("/authenticated/**")
            .access("hasAnyAuthority('ROLE_USER','SCOPE_READ')")
            .expressionHandler(oauthExpressionHandler())
            .accessDecisionManager(accessDecisionManager())
            .and()
            .exceptionHandling().accessDeniedHandler(oauthAccessDeniedHandler)
            .defaultAuthenticationEntryPointFor(oauthAuthenticationEntryPoint(), new AntPathRequestMatcher("/authenticated/**"));               
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
            .tokenServices(tokenServices);
        //.expressionHandler(oauthExpressionHandler);
    }

    @Bean
    public OAuth2WebSecurityExpressionHandler oauthExpressionHandler() {
        return new OAuth2WebSecurityExpressionHandler();
    }

    @Bean
    protected AuthenticationEntryPoint oauthAuthenticationEntryPoint() {
        OAuth2AuthenticationEntryPoint entryPoint = new OAuth2AuthenticationEntryPoint();
        entryPoint.setRealmName("RealName");
        return entryPoint;
    }

    @Bean
    protected AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters 
          = Arrays.asList(
            new ScopeVoter(),
            new RoleVoter(),
            new WebExpressionVoter(),
            new AuthenticatedVoter());
    
        return new UnanimousBased(decisionVoters);
    }
}

运行时异常

14:52:52 TRACE org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.traceDebug: Writing [{timestamp=Fri Sep 16 14:52:52 WEST 2022, status=500, error=Internal Server Error, exception=java.lang.IllegalArgumentException, trace=java.lang.IllegalArgumentException: Failed to evaluate expression '#oauth2.throwOnError(hasAnyAuthority('ROLE_USER','SCOPE_READ'))'
    at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:33)
    at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:59)
    at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:39)
    at org.springframework.security.access.vote.UnanimousBased.decide(UnanimousBased.java:68)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:239)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:113)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    ...(省略中间栈帧)
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method throwOnError(java.lang.Boolean) on null context object
    at org.springframework.expression.spel.ast.MethodReference.throwIfNotNullSafe(MethodReference.java:154)
    at org.springframework.expression.spel.ast.MethodReference.getValueRef(MethodReference.java:83)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:70)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:117)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:308)
    at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:30)
    ... 67 more
, message=Failed to evaluate expression '#oauth2.throwOnError(hasAnyAuthority('ROLE_USER','SCOPE_READ'))', path=/authenticated/profile}]

移除accessDecisionManager配置后一切正常,但希望使用包含指定投票器的UnanimousBased决策管理器而非默认的AffirmativeBased,询问如何实现及该需求是否合理。

解决方案

问题原因

异常核心是#oauth2表达式上下文为null,因为自定义的WebExpressionVoter没有关联OAuth2WebSecurityExpressionHandler。默认配置中,资源服务器会自动将OAuth2WebSecurityExpressionHandler绑定到WebExpressionVoter,但自定义AccessDecisionManager时,需要手动为WebExpressionVoter设置表达式处理器。

修复步骤

修改accessDecisionManager()方法,为WebExpressionVoter注入OAuth2WebSecurityExpressionHandler:

@Bean
protected AccessDecisionManager accessDecisionManager() {
    WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
    // 绑定OAuth2表达式处理器
    webExpressionVoter.setExpressionHandler(oauthExpressionHandler());
    
    List<AccessDecisionVoter<? extends Object>> decisionVoters 
      = Arrays.asList(
        new ScopeVoter(),
        new RoleVoter(),
        webExpressionVoter,
        new AuthenticatedVoter());

    return new UnanimousBased(decisionVoters);
}

同时,确保在ResourceServerSecurityConfigurer中启用表达式处理器(取消注释):

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources
        .tokenServices(tokenServices)
        .expressionHandler(oauthExpressionHandler());
}

需求合理性

使用UnanimousBased是合理的,它要求所有投票器都投赞成票才允许访问,适合需要严格权限校验的场景。相比默认的AffirmativeBased(只要有一个投票器赞成就通过),UnanimousBased能实现更严格的权限控制,比如同时验证角色和OAuth2 scope的有效性。

内容的提问来源于stack exchange,提问作者LuisFerrolho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:35:40