Issue
I'm having trouble associating an accessDecisionManager
to the httpSecurity
configuration of my resource server and using SpEL oauth2 expressions.
I think I've followed correctly what documentation and several related questions here in SO say on how to implement this, but I keep getting the same error.
Current resource server implementation is the following:
@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);
}
}
With this implementation I keep getting the following error:
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)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
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}]
If I remove accessionDecisionManager
from httpSecurity
everything seems to be working, don't understand why. I'd really like to use an UnanimousBased
using those voters instead of using the default one (as far as I read the default is an AffirmativeBased
). How can this be achieved / Does it make sense?
Don't know if it helps, I'm using spring-security-oauth2-2.3.6 and spring-security-core-5.6.2
Solution
It seems WebExpressionVoter
uses DefaultWebSecurityExpressionHandler
by default, which causes the failure when SpEL evaluates the expression #oauth2
:
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
private final Log logger = LogFactory.getLog(getClass());
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
...
public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
Since this class provides a method to change the expressionHandler used, I just had to change it before adding the WebExpressionVoter
instance to my UnanimousBased
:
@Bean
protected AccessDecisionManager accessDecisionManager() {
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(oauthExpressionHandler);
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new ScopeVoter(),
new RoleVoter(),
webExpressionVoter,
new AuthenticatedVoter());
return new UnanimousBased(decisionVoters);
}
Answered By - LuisFerrolho
Answer Checked By - Clifford M. (JavaFixing Volunteer)