Handle UserRedirectRequiredException (A redirect is required to get the users approval)

31,644

Solution 1

An alternative to @Stilleur's solution is below. It is suggested in an official Spring guide under the heading "Handling the Redirects": https://spring.io/guides/tutorials/spring-boot-oauth2/

@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(
    OAuth2ClientContextFilter filter) {
  FilterRegistrationBean registration = new FilterRegistrationBean();
  registration.setFilter(filter);
  registration.setOrder(-100);
  return registration;
}

Solution 2

Changing .addFilterAfter(oauth2ClientContextFilter, ExceptionTranslationFilter.class); to .addFilterAfter(oauth2ClientContextFilter, SecurityContextPersistenceFilter.class); now makes the unhandled UserRedirectRequiredException getting handled.

Solution 3

Refer to @geg 's answer, add @EnableOAuth2Client is also available if you don't implement customer filter yourself.

Because the official demo says that @EnableOAuth2Client also include a default filter for this.

Handling the Redirects

The last change we need to make is to explicitly support the redirects from our app to Facebook. This is handled in Spring OAuth2 with a servlet Filter, and the filter is already available in the application context because we used @EnableOAuth2Client. All that is needed is to wire the filter up so that it gets called in the right order in our Spring Boot application. To do that we need a FilterRegistrationBean:

Share:
31,644
Cédric M.
Author by

Cédric M.

Enjoy coding, interested in new technologies.

Updated on July 09, 2022

Comments

  • Cédric M.
    Cédric M. almost 2 years

    Introduction

    One week ago, I began the development of an application using the OAuth2 framework (with Spring Boot v1.3.0.M4). A brand new experience for me. So I try to make it as simple as possible to understand it better. I am using Spring Security OAuth2 and I am facing difficulties to use it correctly.

    What I want to do

    Authenticate a user when this one authorize my application. Actually, I don't want him to register on my application so he can freely use it without having to fill boring forms to register.

    Problem encountered

    I can't find a way to handle UserRedirectRequired Exception. Because I don't do it, the user is never redirected to the authorization page and an exception is thrown (and unhandled).


    My application

    StandardController.java

    package org.test.oauth.web;
    
    import java.security.Principal;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class StandardController {
    
        @RequestMapping(value = "/", method = RequestMethod.GET)
        public String getHelloWorld() {
            return "Hello world !";
        }
    
        @RequestMapping(value = "/user", method = RequestMethod.GET)
        public Principal getUser(Principal principal) {
            return principal;
        }
    }
    

    StandardConfiguration.java

    package org.test.oauth.configuration;
    
    import java.util.Arrays;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.oauth2.client.OAuth2ClientContext;
    import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
    import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
    import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
    import org.springframework.security.web.access.ExceptionTranslationFilter;
    
    @Configuration
    @EnableOAuth2Sso
    public class StandardConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private OAuth2ClientContextFilter oauth2ClientContextFilter;
    
        @Autowired
        private OAuth2ClientContext oauth2ClientContext;
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
            .authorizeRequests().antMatchers("/login").anonymous().and()
            .authorizeRequests().anyRequest().authenticated().and()
            .httpBasic().and()
            .addFilterAfter(oauth2ClientContextFilter, ExceptionTranslationFilter.class);
            // @formatter:on
        }
    
    //  org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.security.oauth2.client.OAuth2RestOperations] is defined: expected single matching bean but found 2: restTemplate,userInfoRestTemplate
    //  @Bean
    //  public OAuth2RestOperations restTemplate() {
    //      return new OAuth2RestTemplate(bnetResource(), oauth2ClientContext);
    //  }
    
        @Bean
        public OAuth2ProtectedResourceDetails bnetResource() {
            AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
            resource.setId("bnet");
            resource.setClientId("***");
            resource.setClientSecret("***");
            resource.setAccessTokenUri("https://eu.battle.net/oauth/token");
            resource.setUserAuthorizationUri("https://eu.battle.net/oauth/authorize");
            resource.setScope(Arrays.asList("wow.profile"));
            return resource;
        }
    }
    

    My problem

    When I get on my root application, Spring Security redirects me as I am not authenticated. It redirects me to the login page. Many exceptions are thrown and handled by the Spring Boot default configuration but when the UserRedirectRequiredException is created and thrown, no filter handles it. Debugging my application, I found that the last exception found by my oauth2ClientContextFilter is AccessDeniedException. I doubt that my filter (which is actually the OAuth2ClientContextFilter from the default configuration) is not correctly set in the filter chain.

    Stacktrace

    org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
        at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.getRedirectForAuthorization(AuthorizationCodeAccessTokenProvider.java:347) ~[spring-security-oauth2-2.0.7.RELEASE.jar:na]
        at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:194) ~[spring-security-oauth2-2.0.7.RELEASE.jar:na]
        at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.0.7.RELEASE.jar:na]
        at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.0.7.RELEASE.jar:na]
        at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94) ~[spring-security-oauth2-2.0.7.RELEASE.jar:na]
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176) ~[spring-security-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.23.jar:8.0.23]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.23.jar:8.0.23]
        at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87) ~[spring-web-4.2.0.RELEASE.jar:4.2.0.RELEASE]
    

    Looking at the stacktrace, I tried to change the order of my filter in the filter chain. So I tried to put my OAuth2ClientContextFilter after the OAuth2ClientAuthenticationProcessingFilter. Unfortunately, when I launch the application, an error occurs telling me that the filter is unregistered.

    Change

    .addFilterAfter(oauth2ClientContextFilter, ExceptionTranslationFilter.class);
    

    to

    .addFilterAfter(oauth2ClientContextFilter, OAuth2ClientAuthenticationProcessingFilter.class);
    

    Stacktrace

    2015-08-25 12:05:50.990 ERROR 9132 --- [ost-startStop-1] o.s.b.c.embedded.tomcat.TomcatStarter    : Error starting Tomcat context: org.springframework.beans.factory.UnsatisfiedDependencyException
    2015-08-25 12:05:51.054  WARN 9132 --- [           main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt
    java.lang.IllegalArgumentException: Cannot register after unregistered Filter class org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter
        at org.springframework.security.config.annotation.web.builders.FilterComparator.registerAfter(FilterComparator.java:145) ~[spring-security-config-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.security.config.annotation.web.builders.HttpSecurity.addFilterAfter(HttpSecurity.java:960) ~[spring-security-config-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.test.oauth.configuration.StandardConfiguration.configure(StandardConfiguration.java:36) ~[classes/:na]
        at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.getHttp(WebSecurityConfigurerAdapter.java:199) ~[spring-security-config-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    

    So I ask you to help me getting through this and get rid of this problem. I am aware that there are a lot of questions about this issue that are already answered but it couldn't help me as wanted.

    Thanking you for the time dedicated you took to help me.

    Cédric