Spring Boot OAuth2在1.4.1版上无法正常运行

时间:2016-10-27 11:21:39

标签: java spring-security spring-boot spring-security-oauth2

我在Spring OAuth2上使用Spring Boot 1.4.0。当我请求令牌时,服务器响应为:

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

当我将项目更新到Spring Boot 1.4.1时,服务器响应变为

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

从版本1.4.0到1.4.1有什么变化?我应该怎样做才能让我的请求再次发挥作用?

修改

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);
    }





}

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);
}

资源服务器

/**
 * 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;
}
}

编辑2

有一个名为 ProviderManager 的类。当我请求令牌时,会调用以下方法:

    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;
}

版本1.4.0和1.4.1之间的区别在于,版本1.4.1上的属性 parent 为null,然后,在下面方法的片段中,条件为false,并且该方法抛出 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;
    }
}

编辑3

我发现了这个错误的来源。从Spring Boot 1.4.0更新到1.4.1后,依赖

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

从版本2.0.10更改为2.0.11。如果我在Spring Boot 1.4.1上强制版本为2.0.10,则令牌请求正常工作。因此,它似乎是Spring Security OAuth2的一个问题,而不是来自Spring Boot。

编辑4

我在github上提交了一个示例项目,在这里您可以看到将Spring Boot的版本从1.4.0版更改为1.4.1时我面临的问题

1 个答案:

答案 0 :(得分:2)

这真是一个春天的安全问题。 github上有一个问题。
https://github.com/spring-projects/spring-security-oauth/issues/896