Spring Security /oauth/check_token接口性能问题及优化方案咨询
check_token Endpoint in Spring Security OAuth2 (2.x.x) You've nailed down the exact issue: BCryptPasswordEncoder's password matching is intentionally slow—it’s an adaptive hash algorithm built to resist brute-force attacks, which explains why that matches() call is eating up over a second of your request time. Since you don’t need heavy-duty cryptographic strength for your use case, here are tailored solutions to speed things up:
1. Switch to a Lightweight Password Encoder
If your OAuth2 client secrets are already high-entropy random strings (as they should be), you can skip heavy hashing entirely with a plaintext-compatible encoder.
Option A: NoOpPasswordEncoder (Simplest)
@Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }
Note: Make sure your
oauth_client_detailstable stores client secrets in plaintext, matching the value used in theAuthorization: Basicheader.
Option B: DelegatingPasswordEncoder (Future-Proof)
Spring Security 5+ recommends this approach for easy future changes:
@Bean public PasswordEncoder passwordEncoder() { Map<String, PasswordEncoder> encoderMap = new HashMap<>(); encoderMap.put("noop", NoOpPasswordEncoder.getInstance()); return new DelegatingPasswordEncoder("noop", encoderMap); }
This lets you seamlessly switch back to a stronger encoder later if your security needs change.
2. Reduce BCrypt's Work Factor
If you want to keep BCrypt but minimize latency, lower its work factor (default is 10; smaller values mean faster hashing):
@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { // Work factor 4 is drastically faster than the default 10 (tradeoff: lower security) return new BCryptPasswordEncoder(4); }
Important: You’ll need to re-encode all client secrets with this new work factor and update them in your database—otherwise, password matches will fail.
3. Cache Client Authentication Results
OAuth2 client details rarely change, so cache the loaded ClientDetails to avoid repeated database queries and password matches. Use Spring Cache for this:
Step 1: Create a Cached ClientDetailsService
@Service public class CachedClientDetailsService implements ClientDetailsService { private final ClientDetailsService jdbcClientDetailsService; private final Cache clientCache; public CachedClientDetailsService(ClientDetailsService jdbcClientDetailsService, CacheManager cacheManager) { this.jdbcClientDetailsService = jdbcClientDetailsService; this.clientCache = cacheManager.getCache("clientDetails"); } @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { // Check cache first to skip DB and password match ClientDetails cachedClient = clientCache.get(clientId, ClientDetails.class); if (cachedClient == null) { cachedClient = jdbcClientDetailsService.loadClientByClientId(clientId); clientCache.put(clientId, cachedClient); } return cachedClient; } }
Step 2: Enable Spring Cache
Add @EnableCaching to your configuration class, and use a cache provider like ConcurrentMapCache (for single-node apps) or Redis (for distributed environments). After the first check_token request, all subsequent calls will pull data directly from the cache.
Bonus: Simplify Internal Client Authentication
If this is an internal service-to-service setup, consider skipping full Basic Auth + password matching entirely:
- Use IP whitelisting for the
check_tokenendpoint - Validate a pre-shared static key with a custom filter
- Switch to mutual TLS (mTLS) for client authentication
These approaches eliminate password matching overhead entirely.
内容的提问来源于stack exchange,提问作者Deepesh Uniyal

