Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 485
Cédric M. Avatar asked Aug 25 '15 10:08

Cédric M.


Video Answer


3 Answers

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;
}
like image 186
geg Avatar answered Sep 27 '22 17:09

geg


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

like image 20
Cédric M. Avatar answered Sep 27 '22 15:09

Cédric M.


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:

like image 33
chendu Avatar answered Sep 27 '22 15:09

chendu