Configuring and Validating Resource IDs in Spring Security OAuth2
What a Resource ID Represents
In the classic Spring Security OAuth2 setup, the system is split into two roles: the Authorization Server (issues tokens) and the Resource Server (hosts protected APIs). Each Resource Server can expose a logical resource identifier (resource_id). Clients can be granted access to specific resource IDs; if a client’s token doesn’t carry the resource ID for a given Resource Server, requests to that server are rejected. If a cleint is configured without any resource IDs, the resource check is skipped and the token is considered valid for all Resource Servers.
Declaring a Resource ID on the Resource Server
Configure every Resource Server instance with a consistent resource ID. If you run multiple instances of the same microservice, they should share the same identifier.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
@Configuration
@EnableResourceServer
public class ApiResourceServer extends ResourceServerConfigurerAdapter {
private static final String ORDERS_API_ID = "orders-api";
@Override
public void configure(ResourceServerSecurityConfigurer security) {
security
.resourceId(ORDERS_API_ID)
.stateless(true);
}
}
Granting Resource Access on the Authorization Server
Clients must be associated with the resource IDs they’re allowed to call. With a JDBC-backed client registry (oauth_client_details), use the resource_ids column to persist a comma-separated list of IDs.
import javax.sql.DataSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final ClientDetailsService clientDetailsService;
public AuthServerConfig(DataSource dataSource, PasswordEncoder encoder) {
JdbcClientDetailsService jdbc = new JdbcClientDetailsService(dataSource);
jdbc.setPasswordEncoder(encoder);
this.clientDetailsService = jdbc;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
}
If you prefer in-memory configuration for quick tests, you can also assign resource IDs directly:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("web-client")
.secret("{noop}secret")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write")
.resourceIds("orders-api");
}
Where the Resource ID Is Enforced
The Resource Server performs the resource check. @EnableResourceServer inserts OAuth2AuthenticationProcessingFilter into the Spring Security filter chain (ahead of FilterSecurityInterceptor). That filter extracts the token and delegates to OAuth2AuthenticationManager, which loads the OAuth2Authentication and consults client details before proceeding.
Inside OAuth2AuthenticationManager, the logic that verifies the resource ID effectively mirrors the following:
// Simplified excerpt of the resource-id check
Collection<String> allowedResources = authentication.getOAuth2Request().getResourceIds();
if (serverResourceId != null
&& allowedResources != null
&& !allowedResources.isEmpty()
&& !allowedResources.contains(serverResourceId)) {
throw new OAuth2AccessDeniedException(
"Invalid token does not contain resource id (" + serverResourceId + ")");
}
When a client has been authorized for resource ID "test-resource" but attempts to call a server identified as "oauth-rs", the response will be similar to:
{"error":"access_denied","error_description":"Invalid token does not contain resource id (oauth-rs)"}