OAuth2 Client Credentials Flow in Java: Complete Implementation

The Client Credentials flow is used for machine-to-machine authentication where a client application accesses protected resources using its own credentials.

1. Manual Implementation (Without Spring Security)

Core OAuth2 Client Implementation

// Token Response DTO @Data public class OAuth2TokenResponse { @JsonProperty("access_token") private String accessToken; @JsonProperty("token_type") private String tokenType; @JsonProperty("expires_in") private Long expiresIn; @JsonProperty("scope") private String scope; @JsonProperty("refresh_token") private String refreshToken; private LocalDateTime issuedAt; private LocalDateTime expiresAt; @JsonIgnore public boolean isExpired() { return expiresAt != null && LocalDateTime.now().isAfter(expiresAt); } @JsonIgnore public boolean willExpireSoon() { return expiresAt != null && LocalDateTime.now().isAfter(expiresAt.minusSeconds(30)); } @JsonProperty("expires_in") public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; this.issuedAt = LocalDateTime.now(); this.expiresAt = this.issuedAt.plusSeconds(expiresIn); } } // OAuth2 Client Service @Service @Slf4j public class OAuth2ClientService { private final OAuth2Config config; private final RestTemplate restTemplate; private final ObjectMapper objectMapper; private OAuth2TokenResponse cachedToken; private final Object tokenLock = new Object(); public OAuth2ClientService(OAuth2Config config, RestTemplate restTemplate, ObjectMapper objectMapper) { this.config = config; this.restTemplate = restTemplate; this.objectMapper = objectMapper; } public String getAccessToken() { synchronized (tokenLock) { if (cachedToken != null && !cachedToken.willExpireSoon()) { log.debug("Using cached access token"); return cachedToken.getAccessToken(); } try { log.info("Requesting new access token from: {}", config.getTokenUri()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setBasicAuth(config.getClientId(), config.getClientSecret()); MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); body.add("grant_type", "client_credentials"); body.add("scope", String.join(" ", config.getScopes())); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.postForEntity( config.getTokenUri(), request, String.class); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { OAuth2TokenResponse tokenResponse = objectMapper.readValue( response.getBody(), OAuth2TokenResponse.class); cachedToken = tokenResponse; log.info("Successfully obtained access token, expires in: {} seconds", tokenResponse.getExpiresIn()); return tokenResponse.getAccessToken(); } else { log.error("Failed to obtain access token. Status: {}, Body: {}", response.getStatusCode(), response.getBody()); throw new OAuth2Exception("Failed to obtain access token: " + response.getStatusCode()); } } catch (HttpClientErrorException e) { log.error("HTTP client error while obtaining token: {}", e.getResponseBodyAsString()); throw new OAuth2Exception("HTTP client error: " + e.getMessage(), e); } catch (Exception e) { log.error("Unexpected error while obtaining token", e); throw new OAuth2Exception("Unexpected error: " + e.getMessage(), e); } } } public void clearCachedToken() { synchronized (tokenLock) { cachedToken = null; log.info("Cleared cached access token"); } } public OAuth2TokenResponse getTokenInfo() { return cachedToken; } } // Configuration Class @Data @Configuration @ConfigurationProperties(prefix = "app.oauth2") public class OAuth2Config { private String tokenUri; private String clientId; private String clientSecret; private List<String> scopes = List.of("read", "write"); private int connectTimeout = 5000; private int readTimeout = 10000; } // Custom Exceptions public class OAuth2Exception extends RuntimeException { public OAuth2Exception(String message) { super(message); } public OAuth2Exception(String message, Throwable cause) { super(message, cause); } }

HTTP Client Configuration

@Configuration public class HttpClientConfig { @Bean public RestTemplate oauth2RestTemplate(OAuth2Config config) { RestTemplate restTemplate = new RestTemplate(); // Configure timeouts HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(config.getConnectTimeout()); factory.setReadTimeout(config.getReadTimeout()); restTemplate.setRequestFactory(factory); // Add logging interceptor restTemplate.getInterceptors().add(new LoggingInterceptor()); // Add error handler restTemplate.setErrorHandler(new OAuth2ErrorHandler()); return restTemplate; } // Logging Interceptor private static class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { log.debug("OAuth2 Request: {} {}", request.getMethod(), request.getURI()); if (log.isDebugEnabled() && body.length > 0) { log.debug("Request body: {}", new String(body, StandardCharsets.UTF_8)); } ClientHttpResponse response = execution.execute(request, body); log.debug("OAuth2 Response: {}", response.getStatusCode()); return response; } } // Error Handler private static class OAuth2ErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return response.getStatusCode().isError(); } @Override public void handleError(ClientHttpResponse response) throws IOException { String body = getResponseBody(response); log.error("OAuth2 API error - Status: {}, Body: {}", response.getStatusCode(), body); if (response.getStatusCode().is4xxClientError()) { throw new HttpClientErrorException(response.getStatusCode(), body); } else if (response.getStatusCode().is5xxServerError()) { throw new HttpServerErrorException(response.getStatusCode(), body); } } private String getResponseBody(ClientHttpResponse response) { try { return StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8); } catch (IOException e) { return "Unable to read response body"; } } } }

Protected API Client

@Service @Slf4j public class ProtectedApiClient { private final OAuth2ClientService oauth2Service; private final RestTemplate restTemplate; private final String apiBaseUrl; public ProtectedApiClient(OAuth2ClientService oauth2Service, RestTemplate restTemplate, @Value("${app.api.base-url}") String apiBaseUrl) { this.oauth2Service = oauth2Service; this.restTemplate = restTemplate; this.apiBaseUrl = apiBaseUrl; } public <T> T get(String path, Class<T> responseType) { return executeWithToken(token -> { String url = buildUrl(path); log.debug("GET request to: {}", url); HttpHeaders headers = createHeaders(token); HttpEntity<?> entity = new HttpEntity<>(headers); ResponseEntity<T> response = restTemplate.exchange( url, HttpMethod.GET, entity, responseType); return response.getBody(); }); } public <T> T post(String path, Object requestBody, Class<T> responseType) { return executeWithToken(token -> { String url = buildUrl(path); log.debug("POST request to: {}", url); HttpHeaders headers = createHeaders(token); HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<T> response = restTemplate.exchange( url, HttpMethod.POST, entity, responseType); return response.getBody(); }); } public <T> T put(String path, Object requestBody, Class<T> responseType) { return executeWithToken(token -> { String url = buildUrl(path); log.debug("PUT request to: {}", url); HttpHeaders headers = createHeaders(token); HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<T> response = restTemplate.exchange( url, HttpMethod.PUT, entity, responseType); return response.getBody(); }); } public void delete(String path) { executeWithToken(token -> { String url = buildUrl(path); log.debug("DELETE request to: {}", url); HttpHeaders headers = createHeaders(token); HttpEntity<?> entity = new HttpEntity<>(headers); restTemplate.exchange(url, HttpMethod.DELETE, entity, Void.class); return null; }); } private <T> T executeWithToken(Function<String, T> operation) { try { String accessToken = oauth2Service.getAccessToken(); return operation.apply(accessToken); } catch (HttpClientErrorException.Unauthorized e) { log.warn("Access token expired, clearing cache and retrying"); oauth2Service.clearCachedToken(); // Retry with new token String newAccessToken = oauth2Service.getAccessToken(); return operation.apply(newAccessToken); } catch (Exception e) { log.error("API request failed", e); throw new ApiClientException("API request failed: " + e.getMessage(), e); } } private HttpHeaders createHeaders(String accessToken) { HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); return headers; } private String buildUrl(String path) { return apiBaseUrl + path; } } // API Client Exception public class ApiClientException extends RuntimeException { public ApiClientException(String message) { super(message); } public ApiClientException(String message, Throwable cause) { super(message, cause); } }

2. Spring Security OAuth2 Client Implementation

Dependencies (pom.xml)

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>

Application Configuration (application.yml)

spring: security: oauth2: client: registration: my-api-client: client-id: ${CLIENT_ID:test-client} client-secret: ${CLIENT_SECRET:test-secret} authorization-grant-type: client_credentials scope: read,write client-authentication-method: client_secret_basic provider: my-auth-server: token-uri: ${TOKEN_URI:http://localhost:8081/oauth2/token} app: api: base-url: http://localhost:8082/api

Spring Security Configuration

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(authz -> authz .anyRequest().authenticated() ) .oauth2Client(withDefaults()); return http.build(); } @Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .clientCredentials() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; } }

Spring WebClient Implementation

@Configuration public class WebClientConfig { @Bean public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId("my-api-client"); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .filter(logRequest()) .filter(logResponse()) .build(); } private ExchangeFilterFunction logRequest() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { log.info("Request: {} {}", clientRequest.method(), clientRequest.url()); clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.debug("{}: {}", name, value))); return Mono.just(clientRequest); }); } private ExchangeFilterFunction logResponse() { return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { log.info("Response: {}", clientResponse.statusCode()); return Mono.just(clientResponse); }); } } @Service public class SpringOAuth2ApiClient { private final WebClient webClient; public SpringOAuth2ApiClient(WebClient webClient) { this.webClient = webClient; } public <T> Mono<T> get(String path, Class<T> responseType) { return webClient.get() .uri(path) .retrieve() .bodyToMono(responseType) .doOnSubscribe(sub -> log.debug("GET request to: {}", path)) .doOnSuccess(response -> log.debug("GET request successful")) .doOnError(error -> log.error("GET request failed", error)); } public <T> Mono<T> post(String path, Object body, Class<T> responseType) { return webClient.post() .uri(path) .bodyValue(body) .retrieve() .bodyToMono(responseType) .doOnSubscribe(sub -> log.debug("POST request to: {}", path)) .doOnSuccess(response -> log.debug("POST request successful")) .doOnError(error -> log.error("POST request failed", error)); } public <T> Mono<T> put(String path, Object body, Class<T> responseType) { return webClient.put() .uri(path) .bodyValue(body) .retrieve() .bodyToMono(responseType) .doOnSubscribe(sub -> log.debug("PUT request to: {}", path)) .doOnSuccess(response -> log.debug("PUT request successful")) .doOnError(error -> log.error("PUT request failed", error)); } public Mono<Void> delete(String path) { return webClient.delete() .uri(path) .retrieve() .bodyToMono(Void.class) .doOnSubscribe(sub -> log.debug("DELETE request to: {}", path)) .doOnSuccess(response -> log.debug("DELETE request successful")) .doOnError(error -> log.error("DELETE request failed", error)); } }

3. Advanced Features

Token Refresh Scheduler

@Component @Slf4j public class TokenRefreshScheduler { private final OAuth2ClientService oauth2Service; public TokenRefreshScheduler(OAuth2ClientService oauth2Service) { this.oauth2Service = oauth2Service; } @Scheduled(fixedRate = 300000) // 5 minutes public void refreshTokenIfNeeded() { try { OAuth2TokenResponse token = oauth2Service.getTokenInfo(); if (token != null && token.willExpireSoon()) { log.info("Token will expire soon, refreshing..."); oauth2Service.clearCachedToken(); oauth2Service.getAccessToken(); // This will get a new token } } catch (Exception e) { log.error("Error in token refresh scheduler", e); } } } @Configuration @EnableScheduling public class SchedulingConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(2); scheduler.setThreadNamePrefix("oauth2-scheduler-"); return scheduler; } }

Circuit Breaker Implementation

@Service @Slf4j public class ResilientOAuth2Client { private final OAuth2ClientService oauth2Service; private final CircuitBreaker circuitBreaker; public ResilientOAuth2Client(OAuth2ClientService oauth2Service, CircuitBreakerRegistry circuitBreakerRegistry) { this.oauth2Service = oauth2Service; this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("oauth2-client"); } public String getAccessTokenWithCircuitBreaker() { return circuitBreaker.executeSupplier(() -> { try { return oauth2Service.getAccessToken(); } catch (OAuth2Exception e) { log.warn("OAuth2 token request failed", e); throw new CallNotPermittedException("OAuth2 service unavailable", circuitBreaker.getState()); } }); } public <T> T executeWithRetry(Supplier<T> operation) { Retry retry = Retry.of("oauth2-retry", RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofSeconds(2)) .retryOnException(e -> e instanceof OAuth2Exception) .build()); return retry.executeSupplier(() -> { try { return operation.get(); } catch (Exception e) { log.warn("Operation failed, will retry", e); oauth2Service.clearCachedToken(); // Clear token on failure throw e; } }); } } @Configuration public class ResilienceConfig { @Bean public CircuitBreakerRegistry circuitBreakerRegistry() { CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(60)) .slidingWindowSize(10) .build(); return CircuitBreakerRegistry.of(config); } }

4. REST Controller Examples

OAuth2 Management Controller

@RestController @RequestMapping("/api/oauth2") @Slf4j public class OAuth2ManagementController { private final OAuth2ClientService oauth2Service; public OAuth2ManagementController(OAuth2ClientService oauth2Service) { this.oauth2Service = oauth2Service; } @GetMapping("/token") public ResponseEntity<Map<String, Object>> getTokenInfo() { try { String token = oauth2Service.getAccessToken(); OAuth2TokenResponse tokenInfo = oauth2Service.getTokenInfo(); Map<String, Object> response = new HashMap<>(); response.put("access_token", maskToken(token)); response.put("token_type", tokenInfo.getTokenType()); response.put("expires_in", tokenInfo.getExpiresIn()); response.put("scope", tokenInfo.getScope()); response.put("issued_at", tokenInfo.getIssuedAt()); response.put("expires_at", tokenInfo.getExpiresAt()); return ResponseEntity.ok(response); } catch (OAuth2Exception e) { log.error("Failed to get token info", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("error", e.getMessage())); } } @PostMapping("/token/refresh") public ResponseEntity<Map<String, Object>> refreshToken() { try { oauth2Service.clearCachedToken(); String newToken = oauth2Service.getAccessToken(); Map<String, Object> response = new HashMap<>(); response.put("message", "Token refreshed successfully"); response.put("access_token", maskToken(newToken)); return ResponseEntity.ok(response); } catch (OAuth2Exception e) { log.error("Failed to refresh token", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("error", e.getMessage())); } } @GetMapping("/health") public ResponseEntity<Map<String, Object>> healthCheck() { try { OAuth2TokenResponse token = oauth2Service.getTokenInfo(); boolean healthy = token != null && !token.isExpired(); Map<String, Object> health = new HashMap<>(); health.put("status", healthy ? "UP" : "DOWN"); health.put("token_available", token != null); health.put("token_expired", token != null && token.isExpired()); health.put("timestamp", LocalDateTime.now()); return ResponseEntity.status(healthy ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE) .body(health); } catch (Exception e) { log.error("Health check failed", e); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(Map.of("status", "DOWN", "error", e.getMessage())); } } private String maskToken(String token) { if (token == null || token.length() <= 8) { return "***"; } return token.substring(0, 4) + "..." + token.substring(token.length() - 4); } }

Resource Controller

@RestController @RequestMapping("/api/resources") @Slf4j public class ResourceController { private final ProtectedApiClient apiClient; public ResourceController(ProtectedApiClient apiClient) { this.apiClient = apiClient; } @GetMapping("/users") public ResponseEntity<List<User>> getUsers() { try { List<User> users = apiClient.get("/users", List.class); return ResponseEntity.ok(users); } catch (ApiClientException e) { log.error("Failed to get users", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/users/{id}") public ResponseEntity<User> getUser(@PathVariable String id) { try { User user = apiClient.get("/users/" + id, User.class); return ResponseEntity.ok(user); } catch (ApiClientException e) { log.error("Failed to get user: {}", id, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/users") public ResponseEntity<User> createUser(@RequestBody @Valid User user) { try { User createdUser = apiClient.post("/users", user, User.class); return ResponseEntity.status(HttpStatus.CREATED).body(createdUser); } catch (ApiClientException e) { log.error("Failed to create user", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } } @Data class User { private String id; private String username; private String email; private String firstName; private String lastName; }

5. Testing

Unit Tests

@ExtendWith(MockitoExtension.class) class OAuth2ClientServiceTest { @Mock private RestTemplate restTemplate; @Mock private ObjectMapper objectMapper; private OAuth2ClientService oauth2Service; private OAuth2Config config; @BeforeEach void setUp() { config = new OAuth2Config(); config.setTokenUri("http://auth-server.com/token"); config.setClientId("test-client"); config.setClientSecret("test-secret"); config.setScopes(List.of("read", "write")); oauth2Service = new OAuth2ClientService(config, restTemplate, objectMapper); } @Test void shouldGetAccessTokenSuccessfully() throws Exception { // Given OAuth2TokenResponse tokenResponse = new OAuth2TokenResponse(); tokenResponse.setAccessToken("test-token-123"); tokenResponse.setTokenType("Bearer"); tokenResponse.setExpiresIn(3600L); ResponseEntity<String> mockResponse = ResponseEntity.ok("token-response"); when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))) .thenReturn(mockResponse); when(objectMapper.readValue(anyString(), eq(OAuth2TokenResponse.class))) .thenReturn(tokenResponse); // When String token = oauth2Service.getAccessToken(); // Then assertThat(token).isEqualTo("test-token-123"); verify(restTemplate).postForEntity( eq("http://auth-server.com/token"), any(HttpEntity.class), eq(String.class)); } @Test void shouldUseCachedTokenWhenNotExpired() throws Exception { // Given - First call OAuth2TokenResponse tokenResponse = new OAuth2TokenResponse(); tokenResponse.setAccessToken("cached-token"); tokenResponse.setExpiresIn(3600L); // 1 hour ResponseEntity<String> mockResponse = ResponseEntity.ok("token-response"); when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))) .thenReturn(mockResponse); when(objectMapper.readValue(anyString(), eq(OAuth2TokenResponse.class))) .thenReturn(tokenResponse); // When - Make two calls oauth2Service.getAccessToken(); oauth2Service.getAccessToken(); // Then - Should only make one REST call verify(restTemplate, times(1)) .postForEntity(anyString(), any(HttpEntity.class), eq(String.class)); } } @WebMvcTest(OAuth2ManagementController.class) class OAuth2ManagementControllerTest { @Autowired private MockMvc mockMvc; @MockBean private OAuth2ClientService oauth2Service; @Test void shouldReturnTokenInfo() throws Exception { // Given OAuth2TokenResponse tokenResponse = new OAuth2TokenResponse(); tokenResponse.setAccessToken("test-token"); tokenResponse.setTokenType("Bearer"); tokenResponse.setExpiresIn(3600L); tokenResponse.setScope("read write"); when(oauth2Service.getAccessToken()).thenReturn("test-token"); when(oauth2Service.getTokenInfo()).thenReturn(tokenResponse); // When & Then mockMvc.perform(get("/api/oauth2/token")) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").value("test...oken")) .andExpect(jsonPath("$.token_type").value("Bearer")) .andExpect(jsonPath("$.expires_in").value(3600)); } }

Integration Test with WireMock

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = { "app.oauth2.token-uri=http://localhost:${wiremock.server.port}/oauth2/token", "app.oauth2.client-id=test-client", "app.oauth2.client-secret=test-secret" }) @AutoConfigureWireMock(port = 0) class OAuth2IntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test void shouldObtainTokenFromAuthServer() { // Given String tokenResponse = """ { "access_token": "integration-test-token", "token_type": "Bearer", "expires_in": 3600, "scope": "read write" } """; stubFor(post(urlEqualTo("/oauth2/token")) .withBasicAuth("test-client", "test-secret") .withRequestBody(containing("grant_type=client_credentials")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(tokenResponse))); // When ResponseEntity<Map> response = restTemplate.getForEntity("/api/oauth2/token", Map.class); // Then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); assertThat(response.getBody().get("access_token")).isNotNull(); verify(postRequestedFor(urlEqualTo("/oauth2/token"))); } }

6. Application Properties

# application.yml app: oauth2: token-uri: ${TOKEN_URI:http://localhost:8081/oauth2/token} client-id: ${CLIENT_ID:client-id} client-secret: ${CLIENT_SECRET:client-secret} scopes: read,write connect-timeout: 5000 read-timeout: 10000 api: base-url: ${API_BASE_URL:http://localhost:8082/api} server: port: 8080 logging: level: com.example.oauth2: DEBUG org.springframework.web.client: DEBUG management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always

This comprehensive implementation provides a complete OAuth2 Client Credentials flow in Java with both manual and Spring Security approaches, including advanced features like token refresh scheduling, circuit breakers, and comprehensive testing.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper