我在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时,您将能够看到我所面临的问题
这确实是一个spring的oauth安全问题。github上有一个未解决的问题。 https://github.com/spring-projects/spring-security- oauth/issues/896