Skip to content

FilterInvocation#getFullRequestUrl can result in UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported #10694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sguillope opened this issue Jan 11, 2022 · 3 comments
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue

Comments

@sguillope
Copy link

Describe the bug
Calling FilterInvocation#getFullRequestUrl can throw UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported.
Since upgrading Spring Boot from 2.5.x to 2.6.2 (Spring Security from 5.5 to 5.6.1) we have noticed this exception being thrown when a request results in an error. It seems like the introduction of ErrorPageSecurityFilter by Spring Boot has changed the way a request is handled in the case of an error. In which case we end up with a FilterInvocation that has a DummyRequest without support for getScheme.

Stacktrace below. (MyVoter and MyRequestAuthenticationFilter are classes that have been redacted)

java.lang.RuntimeException: java.lang.UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported
	at io.undertow.servlet.spec.RequestDispatcherImpl.error(RequestDispatcherImpl.java:510)
	at io.undertow.servlet.spec.RequestDispatcherImpl.error(RequestDispatcherImpl.java:417)
	at io.undertow.servlet.spec.HttpServletResponseImpl.doErrorDispatch(HttpServletResponseImpl.java:165)
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:282)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1449)
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported
	at org.springframework.security.web.FilterInvocation$UnsupportedOperationExceptionInvocationHandler.invoke(FilterInvocation.java:326)
	at com.sun.proxy.$Proxy231.getScheme(Unknown Source)
	at javax.servlet.ServletRequestWrapper.getScheme(ServletRequestWrapper.java:190)
	at org.springframework.security.web.util.UrlUtils.buildFullRequestUrl(UrlUtils.java:39)
	at org.springframework.security.web.FilterInvocation.getFullRequestUrl(FilterInvocation.java:119)
	at com.example.MyVoter.vote(MyVoter.java:86)
	at com.example.MyVoter.vote(MyVoter.java:46)
	at org.springframework.security.access.vote.UnanimousBased.decide(UnanimousBased.java:68)
	at org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator.isAllowed(DefaultWebInvocationPrivilegeEvaluator.java:100)
	at org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator.isAllowed(DefaultWebInvocationPrivilegeEvaluator.java:67)
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.isAllowed(ErrorPageSecurityFilter.java:84)
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.doFilter(ErrorPageSecurityFilter.java:72)
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.doFilter(ErrorPageSecurityFilter.java:66)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106)
	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:87)
	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.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109)
	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 com.example.MyRequestAuthenticationFilter.doFilter(MyRequestAuthenticationFilter.java:79)
	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.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 io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.cloud.sleuth.instrument.web.servlet.TracingFilter.doFilter(TracingFilter.java:68)
	at org.springframework.cloud.sleuth.autoconfig.instrument.web.TraceWebServletConfiguration$LazyTracingFilter.doFilter(TraceWebServletConfiguration.java:129)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:257)
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchToPath(ServletInitialHandler.java:182)
	at io.undertow.servlet.spec.RequestDispatcherImpl.error(RequestDispatcherImpl.java:504)
	... 19 common frames omitted

To Reproduce
Call FilterInvocation#getFullRequestUrl from an AccessDecisionVoter#vote method

Expected behavior
No exception is thrown

Sample

No sample as of yet.

@sguillope sguillope added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Jan 11, 2022
@marcusdacoregio marcusdacoregio changed the title FilterInvocation#getFullRequestUrl can result in "UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported" FilterInvocation#getFullRequestUrl can result in UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getScheme() is not supported Jan 11, 2022
@marcusdacoregio
Copy link
Contributor

Thanks for the report @sguillope.

Can you share how you are using the MyFilter?

@marcusdacoregio marcusdacoregio self-assigned this Jan 11, 2022
@marcusdacoregio marcusdacoregio added in: web An issue in web modules (web, webmvc) and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 11, 2022
@sguillope
Copy link
Author

Hi @marcusdacoregio, I can't give the exact code but essentially it looks like this:

public class MyRequestAuthenticationFilter extends GenericFilterBean {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (requiresAuthentication((HttpServletRequest)request)) {
            doAuthenticate((HttpServletRequest)request, (HttpServletResponse)response);
        }

        chain.doFilter(request, response); // line 79
    }

    private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
        if (// can authenticate?) {
           // create Authentication object
           // SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
           // do nothing
        }
    }

    private boolean requiresAuthentication(HttpServletRequest request) {
        return true || false; // actual logic based on request
    }
}

So there isn't anything fancy. The exception would only happen in the case where we did authenticate successfully but the resource was not found. In which case Spring Boot's ErrorPageSecurityFilter checks for permissions for /error and ends up calling out to our MyVoter which itself calls FilterInvocation#getFullRequestUrl which uses the DummyRequest.
So right now all authenticate requests that should result in a 404, result in a 500 because of our usage of FilterInvocation#getFullRequestUrl.

@marcusdacoregio
Copy link
Contributor

Hi @sguillope, we are investigating alternatives to the ErrorPageSecurityFilter here #10919.

I recommend you to take a look at this comment where I provided some workaround.

I'm closing this so we can centralize the comments and effort in just one ticket. Thank you very much for the report and stay tuned to the other tickets.

@marcusdacoregio marcusdacoregio added status: duplicate A duplicate of another issue and removed type: bug A general bug labels Apr 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

2 participants