Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot OAuth2 not working properly on version 1.4.1

I was using Spring Boot 1.4.0 with Spring OAuth2. When I requested a token, the server response was:

  {
  "access_token": "93f8693a-22d2-4139-a4ea-d787f2630f04",
  "token_type": "bearer",
  "refresh_token": "2800ea24-bb4a-4a01-ba87-2d114c1a2235",
  "expires_in": 899,
  "scope": "read write"
  }

When I updated my project to Spring Boot 1.4.1, the server response became

  {
  "error": "invalid_client",
  "error_description": "Bad client credentials"
  }

What was changed from version 1.4.0 to 1.4.1 ? And what should I do to make my request work again?

EDIT

WebSecurityConfiguration:

 @Configuration
 @EnableWebSecurity
 public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{

 /** The client details service. */
    @Autowired
    private ClientDetailsService clientDetailsService;

    /** The password encoder. */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /** The custom authentication provider. */
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    /** The o auth 2 token store service. */
    @Autowired
    private OAuth2TokenStoreService oAuth2TokenStoreService;

    /**
     * User details service.
     *
     * @return the user details service
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetailsService userDetailsService = new ClientDetailsUserDetailsService(clientDetailsService);
        return userDetailsService;
    }

    /**
     * Register authentication.
     *
     * @param auth the auth
     */
    @Autowired
    protected void registerAuthentication(final AuthenticationManagerBuilder auth) {
        try {
            auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
        } catch (Exception e) {
            LOGGER.error("Não foi possível registrar o AuthenticationManagerBuilder.", e);
        }
    }

    /**
     * Authentication manager bean.
     *
     * @return the authentication manager
     * @throws Exception the exception
     */
    @Override
    @Bean(name = "authenticationManagerBean")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * Authentication manager.
     *
     * @return the authentication manager
     * @throws Exception the exception
     */
    @Override
    @Bean(name = "authenticationManager")
    protected AuthenticationManager authenticationManager() throws Exception {
        UserAuthenticationManager userAuthenticationManager = new UserAuthenticationManager();
        userAuthenticationManager.setCustomAuthenticationProvider(customAuthenticationProvider);
        return userAuthenticationManager;
    }

    /**
     * User approval handler.
     *
     * @param tokenStore the token store
     * @return the token store user approval handler
     */
    @Bean
    @Autowired
    public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
        TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
        handler.setTokenStore(oAuth2TokenStoreService);
        handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
        handler.setClientDetailsService(clientDetailsService);
        return handler;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // TODO Auto-generated method stub
        super.configure(auth);
    }





}

The OAuth2Config

@Configuration
@EnableAuthorizationServer
@Order(LOWEST_PRECEDENCE - 100)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter  {

/** The token store. */
@Autowired
private TokenStore tokenStore;

/** The user approval handler. */
@Autowired
private UserApprovalHandler userApprovalHandler;

/** The authentication manager. */
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;

/**
 * Para ativar o Authorization Basic remova a seguinte linha: security allowFormAuthenticationForClients()
 * 
 * @see http://stackoverflow.com/questions/26881296/spring-security-oauth2-full-authentication-is-required-to-access-this-resource
 * 
 */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.allowFormAuthenticationForClients();
}

/* (non-Javadoc)
 * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer)
 */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler).authenticationManager(authenticationManager);
}

Resource Server

/**
 * The Class ResourceServer.
 */
@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {

private static final String CLIENTE_AUTHENTICATED_READ = "#oauth2.clientHasRole('ROLE_TRUSTED_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')";

private static final String CLIENTE_AUTHENTICATED_WRITE = "#oauth2.clientHasRole('ROLE_TRUSTED_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('write')";

private static final String CONTADOR_AUTHENTICATED_READ = "#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isUser() and #oauth2.hasScope('read')";

private static final String CONTADOR_AUTHENTICATED_WRITE = "#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isUser() and #oauth2.hasScope('write')";

private static final String CONTADOR_OR_CLIENTE_AUTHENTICATED_READ = "(#oauth2.clientHasRole('ROLE_TRUSTED_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')) or (#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isUser() and #oauth2.hasScope('read'))";

private static final String CONTADOR_OR_CLIENTE_AUTHENTICATED_WRITE = "(#oauth2.clientHasRole('ROLE_TRUSTED_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('write')) or (#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isUser() and #oauth2.hasScope('write'))";

private static final String URL_CONTADOR = "/v1/files/^[\\d\\w]{24}$/contadores/self";

private static final String URL_CLIENTE = "/v1/files/^[\\d\\w]{24}$/contadores/[0-9]{1,}";

/** The client details service. */
@Autowired
private ClientDetailsService clientDetailsService;

/** The o auth 2 token store service. */
@Autowired
private OAuth2TokenStoreService oAuth2TokenStoreService;

/**
 * http.authorizeRequests().antMatchers("/v1/emails").fullyAuthenticated();
 * https://github.com/ShuttleService/shuttle/blob/7a0001cfbed4fbf851f1b27cf1b952b2a37c1bb8/src/main/java/com/real/apps/shuttle/security/SecurityConfig.java
 * 
 * @see org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)
 * 
 */
@Override
public void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http.sessionManagement().sessionCreationPolicy(STATELESS).and().
        authorizeRequests()
        //===========================COMUNS (SEM AUTORIZAÇÃO) ===============//
        .antMatchers(POST, "/oauth/token").anonymous()
        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

        //===========================FILE CONTROLLER=========================//
        //===========================CONTADOR================================//
        .antMatchers(POST,     "/v1/files/randomId/contadores/self").access(CONTADOR_AUTHENTICATED_WRITE)
        .regexMatchers(PUT,    URL_CONTADOR).access(CONTADOR_AUTHENTICATED_WRITE)
        .regexMatchers(GET,    URL_CONTADOR).access(CONTADOR_AUTHENTICATED_READ)
        .regexMatchers(DELETE, URL_CONTADOR).access(CONTADOR_AUTHENTICATED_WRITE)

        //===========================CLIENTE=================================//
        .regexMatchers(POST,   "/v1/files/randomId/contadores/[0-9]{1,}").access(CLIENTE_AUTHENTICATED_WRITE)
        .regexMatchers(PUT,    URL_CLIENTE).access(CLIENTE_AUTHENTICATED_WRITE)
        .regexMatchers(GET,    URL_CLIENTE).access(CLIENTE_AUTHENTICATED_READ)
        .regexMatchers(DELETE, URL_CLIENTE).access(CLIENTE_AUTHENTICATED_WRITE)

        //===========================METADATA CONTROLLER=====================//
        .antMatchers(GET,      "/v1/metadatas/").access(CONTADOR_OR_CLIENTE_AUTHENTICATED_READ)
        .regexMatchers(GET,    "/v1/metadatas/^[\\d\\w]{24}$").access(CONTADOR_OR_CLIENTE_AUTHENTICATED_READ)
        .regexMatchers(GET,    "/v1/metadatas/self/folders/^[\\d\\w]{24}$").access(CONTADOR_OR_CLIENTE_AUTHENTICATED_READ)

        //===========================FOLDER CONTROLLER=======================//
        .regexMatchers(PUT,    "/v1/folders/^[\\d\\w]{24}$/contadores/self/lock").access(CONTADOR_AUTHENTICATED_WRITE)
        .regexMatchers(PUT,    "/v1/folders/^[\\d\\w]{24}$/contadores/self/unlock").access(CONTADOR_AUTHENTICATED_WRITE)
        .regexMatchers(GET,    "/v1/folders/^[\\d\\w]{24}$").access(CONTADOR_AUTHENTICATED_READ)

        //===========================ESPAÇO CONTROLLER=======================//
        .antMatchers(GET,      "/v1/espacos/contadores/self").access(CONTADOR_AUTHENTICATED_READ)

        //===========================OBRIGACAO CONTROLLER====================//
        .antMatchers(GET,      "/v1/obrigacoes").access(CONTADOR_AUTHENTICATED_READ)
        .antMatchers(POST,     "/v1/obrigacoes").access(CONTADOR_OR_CLIENTE_AUTHENTICATED_WRITE)

        //===========================PROTOCOLO CONTROLLER===================//
        .regexMatchers(GET,      "/v1/protocolos/^[\\d\\w]{24}$").access(CONTADOR_AUTHENTICATED_READ)
        .and().authorizeRequests().antMatchers("/v1/**").authenticated();





}

/* (non-Javadoc)
 * @see org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer)
 */
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.tokenServices(customTokenServices());
    resources.resourceId("arquivos-upload-api").stateless(false);
}

/**
 * Custom token services.
 *
 * @return the resource server token services
 */
@Primary
@Bean(name = "defaultAuthorizationServerTokenServices")
public ResourceServerTokenServices customTokenServices() {
    final CustomTokenServices defaultTokenServices = new CustomTokenServices();
    defaultTokenServices.setTokenStore(oAuth2TokenStoreService);
    defaultTokenServices.setSupportRefreshToken(true);
    defaultTokenServices.setReuseRefreshToken(false);
    defaultTokenServices.setClientDetailsService(clientDetailsService);
    return defaultTokenServices;
}
}

EDIT 2

There an class called ProviderManager. When I request a token, the method below is called:

    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();

    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }

        if (debug) {
            logger.debug("Authentication attempt using "
                    + provider.getClass().getName());
        }

        try {
            result = provider.authenticate(authentication);

            if (result != null) {
                copyDetails(authentication, result);
                break;
            }
        }
        catch (AccountStatusException e) {
            prepareException(e, authentication);
            // SEC-546: Avoid polling additional providers if auth failure is due to
            // invalid account status
            throw e;
        }
        catch (InternalAuthenticationServiceException e) {
            prepareException(e, authentication);
            throw e;
        }
        catch (AuthenticationException e) {
            lastException = e;
        }
    }

    if (result == null && parent != null) {
        // Allow the parent to try.
        try {
            result = parent.authenticate(authentication);
        }
        catch (ProviderNotFoundException e) {
            // ignore as we will throw below if no other exception occurred prior to
            // calling parent and the parent
            // may throw ProviderNotFound even though a provider in the child already
            // handled the request
        }
        catch (AuthenticationException e) {
            lastException = e;
        }
    }

    if (result != null) {
        if (eraseCredentialsAfterAuthentication
                && (result instanceof CredentialsContainer)) {
            // Authentication is complete. Remove credentials and other secret data
            // from authentication
            ((CredentialsContainer) result).eraseCredentials();
        }

        eventPublisher.publishAuthenticationSuccess(result);
        return result;
    }

    // Parent was null, or didn't authenticate (or throw an exception).

    if (lastException == null) {
        lastException = new ProviderNotFoundException(messages.getMessage(
                "ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
    }

    prepareException(lastException, authentication);

    throw lastException;
}

The difference between version 1.4.0 and 1.4.1 is that the attribute parent is null on version 1.4.1, and then, on the snippet of the method below the condition is false, and the method throws BadClientException

 if (result == null && parent != null) {
    // Allow the parent to try.
    try {
        result = parent.authenticate(authentication);
    }
    catch (ProviderNotFoundException e) {
        // ignore as we will throw below if no other exception occurred prior to
        // calling parent and the parent
        // may throw ProviderNotFound even though a provider in the child already
        // handled the request
    }
    catch (AuthenticationException e) {
        lastException = e;
    }
}

EDIT 3

I've found where this error is coming from. After the update from Spring Boot 1.4.0 to 1.4.1, the dependecy

        <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
       </dependency>

changed from version 2.0.10 to 2.0.11. If I force the version to be 2.0.10 on Spring Boot 1.4.1, the token request works normally. So, it seems to be an issue of Spring Security OAuth2 and not from Spring Boot.

EDIT 4

I commited a sample project on github where you will be able to see what I'm facing when changing the version of spring boot from version 1.4.0 to 1.4.1

like image 779
Gabriel Avatar asked Oct 27 '16 11:10

Gabriel


People also ask

How springboot OAuth2 works internally?

Spring Boot OAuth2 | How Springboot Oauth2 works Internally? In Spring boot, we have one mechanism which helps us to do Authorization; this is called as oauth2.0; by the use of this, we can easily authorize the interaction between two services.

What is the OAuth2 authorization framework?

Introduction Before we dive in the details, let’s take a quick refresher to the Oauth2. Oauth2 is an authorization framework that enables applications to get limited access to user accounts on an HTTP service.

How to secure your REST API with OAuth2?

We will secure our REST API with Oauth2 by building an authorization server to authenticate our client and provide an access_token for future communication. 1. Introduction Before we dive in the details, let’s take a quick refresher to the Oauth2.

What is the role of the resource owner in OAuth?

Oauth defines the four main roles: Resource Owner: User – The resource owner is the user who authorizes an application to access their account Resource / Authorization Server – The resource server hosts the protected user accounts, and the authorization server verifies the identity of the user then issues access tokens to the application.


Video Answer


1 Answers

It is really a spring oauth security problem. There is an issue open on github.
https://github.com/spring-projects/spring-security-oauth/issues/896

like image 83
Silvano Pessoa Avatar answered Nov 08 '22 23:11

Silvano Pessoa