我在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时我面临的问题
答案 0 :(得分:2)
这真是一个春天的安全问题。 github上有一个问题。
https://github.com/spring-projects/spring-security-oauth/issues/896