Handle UserRedirectRequiredException (A redirect i

2019-04-24 23:17发布

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

4条回答
时光不老,我们不散
2楼-- · 2019-04-24 23:33

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

查看更多
贪生不怕死
3楼-- · 2019-04-24 23:39

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:

查看更多
Bombasti
4楼-- · 2019-04-24 23:41

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;
}
查看更多
劫难
5楼-- · 2019-04-24 23:57

For those of you that have this problem in Weblogic: everything runs fine in the embedded Tomcat server but fails when using WebLogic. Assuming that you already configured the deployment in weblogic.xml (here is a nice example), you need to upgrade from WebLogic 12.1.2.0 to WebLogic 12.2.1.2. WL has some issues with libraries particularly with spring-security.

查看更多
登录 后发表回答