Back to Spring Security: JWT, OAuth2, CSRF/CORS
Q3Question 3 of 3

What is OAuth2 Resource Server and WebFlux?

Key Concepts
  • An OAuth2 Resource Server does not issue tokens -- it validates incoming JWTs by fetching the issuer's public key (JWKS) and checking signature, expiry, and claims.
  • @HttpExchange (Spring 6) generates HTTP clients from interfaces -- like Feign but Spring-native with no extra dependencies, using RestClient underneath.
  • WebFlux is the reactive web framework built on Project Reactor: Mono<T> for 0-1 results, Flux<T> for 0-N streamed results, running on a non-blocking event loop.
  • WebSocket provides full-duplex bidirectional real-time communication (live trading terminal); SSE provides simpler one-way server-to-client streaming (price ticker).
  • GraphQL lets the client specify exactly which fields to return, eliminating over-fetching and under-fetching problems common with REST.
  • Spring AI provides a ChatClient abstraction for LLM integration with RAG (Retrieval-Augmented Generation) support.
  • Quarkus does build-time processing (annotation scanning, proxy generation) for faster startup and lower memory, while Spring Boot does most processing at runtime.

Definition

OAuth2 Resource Server validates external JWT tokens, @HttpExchange generates declarative REST clients, WebFlux provides reactive Mono/Flux types, WebSocket enables bidirectional real-time communication, and GraphQL lets clients pick exactly the fields they need.
  • OAuth2 Resource Server -- a Spring Security module that validates JWT tokens from an external authorization server (Keycloak, Auth0, Okta) by fetching the issuer's public key and verifying signature, expiry, issuer, and audience claims.
  • @HttpExchange -- a Spring 6 annotation that generates HTTP client implementations from interfaces, similar to Feign but using Spring's own RestClient; no extra dependencies needed.
  • WebFlux -- Spring's reactive web framework built on Project Reactor; uses Mono for single-value responses and Flux for streaming multiple values, running on Netty's non-blocking event loop.
  • WebSocket -- a protocol that provides full-duplex, bidirectional communication over a single TCP connection; ideal for live trading terminals where both client and server push data.
  • SSE (Server-Sent Events) -- a simpler one-way streaming protocol where the server pushes events to the client over HTTP; the browser auto-reconnects on disconnection.
  • GraphQL -- a query language where the client specifies exactly which fields to return in each request, eliminating the over-fetching and under-fetching problems of REST.

OAuth2 is like showing your Aadhaar card (a JWT issued by the government) at National Bank of India instead of the bank maintaining its own identity system -- the bank just verifies the card is genuine. WebSocket is a live phone call between Rajesh (the trader) and the exchange where both can talk at any time. SSE is the bank texting Priya real-time price updates -- one-way from bank to customer. GraphQL is ordering only the fields you want from the menu instead of getting the entire plate.

Understanding the Concepts

What is an OAuth2 Resource Server?

An OAuth2 Resource Server does not issue tokens -- that is the Authorization Server's job (Keycloak, Auth0, Okta). The Resource Server only validates incoming JWT tokens. When Rajesh sends a request to the National Bank of India account API, his JWT is validated by checking:

  1. The token's digital signature against the issuer's public key (fetched from the JWKS endpoint).
  2. The expiry time (exp claim) to ensure the token has not expired.
  3. The issuer (iss claim) to ensure it came from the trusted authorization server.
  4. The audience (aud claim) to ensure the token was meant for this API.
YAML
# application.yml -- configure OAuth2 Resource Server
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://keycloak.nationalbank.com/realms/banking
          # Spring fetches the JWKS keys from:
          # https://keycloak.nationalbank.com/realms/banking/.well-known/openid-configuration
JAVA
package com.bank.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ResourceServerConfig {

    @Bean
    public SecurityFilterChain securityChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
        return http.build();
    }
}
TEXT
Request:
GET /api/accounts/SAV2001
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Server validates:
1. Signature: valid (matches JWKS public key)
2. Expiry: not expired (exp > now)
3. Issuer: https://keycloak.nationalbank.com/realms/banking (trusted)
4. Scope: includes "read:accounts" (authorized)

Result: 200 OK with account data

What is the declarative HTTP client (@HttpExchange)?

@HttpExchange is Spring 6's built-in alternative to Feign. You declare an interface with annotated methods, and Spring generates the HTTP client implementation using HttpServiceProxyFactory backed by RestClient. No extra dependencies are needed.

At National Bank of India, Rajesh uses @HttpExchange to call the external exchange rate API with a type-safe client.

JAVA
package com.bank.client;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.bind.annotation.RequestBody;
import java.math.BigDecimal;

@HttpExchange("/api/rates")
public interface ExchangeRateClient {

    @GetExchange("/{currency}")
    BigDecimal getRate(@PathVariable String currency);

    @PostExchange("/convert")
    ConversionResult convert(@RequestBody ConversionRequest request);
}
JAVA
package com.bank.config;

import com.bank.client.ExchangeRateClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class HttpClientConfig {

    @Bean
    public ExchangeRateClient exchangeRateClient() {
        RestClient restClient = RestClient.builder()
                .baseUrl("https://rates.nationalbank.com")
                .build();
        HttpServiceProxyFactory factory = HttpServiceProxyFactory
                .builderFor(RestClientAdapter.create(restClient))
                .build();
        return factory.createClient(ExchangeRateClient.class);
    }
}
TEXT
Usage:
BigDecimal usdRate = exchangeRateClient.getRate("USD");
// Generates: GET https://rates.nationalbank.com/api/rates/USD
// Returns: 83.45

What is WebFlux and how does it differ from Spring MVC?

WebFlux is Spring's reactive, non-blocking web framework. It runs on Netty's event loop instead of Tomcat's thread-per-request model. It uses two types from Project Reactor: Mono for 0-1 results, and Flux for 0-N streamed results.

The key difference: Spring MVC blocks a thread while waiting for a database or API response. WebFlux releases the thread and picks up the work when the response arrives. This means a single Netty thread can handle thousands of concurrent connections.

| Aspect | Spring MVC | WebFlux |
|--------|-----------|----------|
| Server | Tomcat (thread-per-request) | Netty (event loop) |
| Blocking | Thread blocked during I/O | Thread released during I/O |
| Types | Object, List | Mono, Flux |
| Concurrency | 200 threads = 200 concurrent requests | 4 event loop threads = thousands of concurrent requests |
| Best for | CRUD APIs, traditional apps | High-concurrency streaming, real-time feeds |
| Learning curve | Lower (imperative style) | Higher (reactive/functional style) |

JAVA
package com.bank.controller;

import com.bank.model.Account;
import com.bank.repository.ReactiveAccountRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class ReactiveAccountController {

    private final ReactiveAccountRepository repo;

    public ReactiveAccountController(ReactiveAccountRepository repo) {
        this.repo = repo;
    }

    @GetMapping("/api/accounts/{id}")
    public Mono<Account> getAccount(@PathVariable String id) {
        return repo.findById(id); // non-blocking, thread not held
    }

    @GetMapping("/api/accounts")
    public Flux<Account> getAllAccounts() {
        return repo.findAll(); // streams results as they become available
    }
}
TEXT
Spring MVC:
  Thread-1 handles request -> blocks 50ms waiting for DB -> returns response -> Thread-1 freed.
  200 concurrent requests need 200 threads.

WebFlux:
  Event-loop thread handles request -> sends DB query -> releases thread -> picks up response later.
  10,000 concurrent requests use 4 event-loop threads.

How do WebSocket and SSE work for real-time communication?

WebSocket provides full-duplex, bidirectional communication over a single persistent TCP connection. Both client and server can push messages at any time. At National Bank of India, the live trading terminal uses WebSocket so Rajesh can submit orders and receive execution confirmations in real time.

SSE (Server-Sent Events) is simpler: the server pushes events to the client over a standard HTTP connection. It is one-way (server to client only) and supports auto-reconnection. Priya's banking app uses SSE to display a live price ticker.

| Aspect | WebSocket | SSE |
|--------|-----------|-----|
| Direction | Bidirectional | Server to client only |
| Protocol | ws:// or wss:// | Standard HTTP |
| Reconnection | Manual (client must implement) | Automatic (browser built-in) |
| Binary data | Supported | Text only |
| Use case | Trading terminal, chat | Price ticker, notifications |

JAVA
package com.bank.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.Random;

@RestController
public class PriceStreamController {

    private final Random random = new Random();

    // SSE endpoint -- streams live prices to the browser
    @GetMapping(value = "/api/prices/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<PriceUpdate> streamPrices() {
        return Flux.interval(Duration.ofSeconds(1))
                .map(tick -> new PriceUpdate(
                        "NIFTY50",
                        BigDecimal.valueOf(22000 + random.nextDouble() * 100)
                                .setScale(2, RoundingMode.HALF_UP)));
    }
}
JAVA
package com.bank.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");    // server pushes to /topic/*
        config.setApplicationDestinationPrefixes("/app"); // client sends to /app/*
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws/trading")
                .setAllowedOrigins("https://portal.nationalbank.com")
                .withSockJS();
    }
}
TEXT
SSE:
  Browser connects: GET /api/prices/stream (Accept: text/event-stream)
  Server sends every second:
    data:{"symbol":"NIFTY50","price":22045.67}
    data:{"symbol":"NIFTY50","price":22051.23}
  Browser auto-reconnects if connection drops.

WebSocket:
  Client connects: ws://portal.nationalbank.com/ws/trading
  Client sends:    {"action":"BUY","symbol":"RELIANCE","qty":100}
  Server sends:    {"status":"EXECUTED","orderId":"ORD-7892"}

What is GraphQL in Spring and when should you use it?

GraphQL lets the client specify exactly which fields to return in each request. With REST, a mobile app and a web app both call the same /api/accounts/SAV2001 endpoint but get the same large JSON response. The mobile app only needs the balance, while the web app needs the full account details. GraphQL solves this by letting each client request only the fields it needs.

Spring for GraphQL (spring-boot-starter-graphql) integrates GraphQL with Spring's controller model.

GRAPHQL
# schema.graphqls
type Query {
    account(id: ID!): Account
}

type Account {
    id: ID!
    customerName: String!
    balance: BigDecimal!
    accountType: String!
    transactions: [Transaction!]
}

type Transaction {
    id: ID!
    amount: BigDecimal!
    description: String!
    timestamp: String!
}
JAVA
package com.bank.controller;

import com.bank.model.Account;
import com.bank.repository.AccountRepository;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
public class AccountGraphQLController {

    private final AccountRepository accountRepository;

    public AccountGraphQLController(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @QueryMapping
    public Account account(@Argument String id) {
        return accountRepository.findById(id).orElseThrow();
    }
}
TEXT
Mobile app query (only balance):
  query { account(id: "SAV2001") { balance } }
  Response: { "data": { "account": { "balance": 150000.00 } } }

Web app query (full details):
  query { account(id: "SAV2001") { id customerName balance accountType transactions { amount description } } }
  Response: full account with transactions

Benefit: mobile app saves bandwidth, web app gets everything it needs -- one endpoint.

How does Quarkus differ from Spring Boot?

Quarkus is an alternative Java framework optimized for cloud-native and serverless deployments. The key difference is when work happens: Spring Boot does most processing at runtime (annotation scanning, proxy generation, auto-configuration), while Quarkus does it at build time.

| Aspect | Spring Boot | Quarkus |
|--------|-----------|----------|
| Processing | Runtime (reflection, proxies) | Build time (code generation) |
| Startup | 2-5 seconds (JVM) | 0.5-1 second (JVM), 0.01s (native) |
| Memory | 150-300 MB | 50-100 MB |
| Native image | Supported (GraalVM) | First-class support (GraalVM) |
| Ecosystem | Largest (Spring ecosystem) | Growing (MicroProfile, SmallRye) |
| Best for | Enterprise apps, large teams | Serverless, microservices, Kubernetes |

TEXT
Spring Boot startup:
  JVM starts -> scans annotations -> creates proxies -> resolves beans -> 3.5 seconds

Quarkus startup:
  JVM starts -> loads pre-built metadata -> 0.8 seconds
  Native image -> 0.015 seconds

What are common mistakes with OAuth2 and WebFlux?

A common mistake is configuring the OAuth2 Resource Server with the wrong issuer URI, which causes all tokens to fail validation silently.

YAML
# WRONG: pointing to the wrong realm
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://keycloak.nationalbank.com/realms/wrong-realm

# CORRECT: match the realm that issues your tokens
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://keycloak.nationalbank.com/realms/banking

With WebFlux, a common mistake is calling a blocking method (like JdbcTemplate or Thread.sleep) inside a reactive pipeline. This blocks the event loop thread and defeats the purpose of reactive programming.

JAVA
// WRONG: blocking call inside reactive pipeline blocks the event loop
public Mono<Account> getAccount(String id) {
    Account account = jdbcTemplate.queryForObject("SELECT...", mapper, id); // BLOCKS
    return Mono.just(account);
}
JAVA
// CORRECT: use a reactive driver (R2DBC) or subscribeOn(Schedulers.boundedElastic())
public Mono<Account> getAccount(String id) {
    return r2dbcTemplate.selectOne(
            Query.query(Criteria.where("id").is(id)), Account.class);
}

What should a senior engineer know?

  • JWT vs opaque tokens. JWTs are self-contained (claims inside the token), so the Resource Server validates them locally. Opaque tokens require a round-trip to the authorization server's introspection endpoint. JWTs are faster but cannot be revoked easily; opaque tokens can be revoked instantly.
  • Custom JWT claim extraction. Override the JwtAuthenticationConverter to map custom claims (like roles stored in a nested object) to Spring Security authorities.
  • WebFlux back-pressure. Flux supports back-pressure: if the consumer is slower than the producer, Reactor signals the producer to slow down. This prevents memory exhaustion in high-throughput streaming.
  • GraphQL N+1 problem. Without a DataLoader, fetching a list of accounts and their transactions triggers a separate query per account. Use Spring's @BatchMapping to batch-load related entities.
  • Spring AI RAG pattern. Retrieval-Augmented Generation stores domain documents in a vector database. When Priya asks a question, the system retrieves relevant documents and includes them in the prompt, grounding the LLM response in factual data.

What are common interview follow-up questions?

  • Can the Resource Server validate tokens without calling the authorization server? (Yes, with JWTs -- once the public key is fetched and cached, validation is local. With opaque tokens, every validation requires a round-trip.)
  • When should you use WebFlux instead of Spring MVC with virtual threads? (WebFlux for streaming APIs (SSE, WebSocket) and when your entire stack is reactive. MVC + virtual threads for traditional request-response APIs where you want simple imperative code.)
  • How do you secure a WebSocket endpoint? (Use Spring Security's WebSocket support: apply authentication on the HTTP upgrade request and use channel interceptors for per-message authorization.)
  • What is the difference between @GetExchange and @GetMapping? (@GetMapping defines a server endpoint; @GetExchange defines a client method that sends a GET request.)

Visual Overview

DIAGRAM
  OAuth2 Resource Server Flow
  ═══════════════════════════
  ┌────────────┐    ┌───────────────────┐    ┌───────────────────┐
  │ Client     │    │ Resource Server   │    │ Authorization     │
  │ (Rajesh)   │    │ (Account API)     │    │ Server (Keycloak) │
  └─────┬──────┘    └────────┬──────────┘    └────────┬──────────┘
        │                    │                        │
        │ 1. Login           │                        │
        │───────────────────────────────────────────► │
        │                    │                        │
        │ 2. JWT token       │                        │
        │◄──────────────────────────────────────────  │
        │                    │                        │
        │ 3. GET /api/accounts/SAV2001                │
        │    Authorization: Bearer eyJ...             │
        │───────────────────►│                        │
        │                    │                        │
        │                    │ 4. Fetch JWKS keys     │
        │                    │    (cached after first)│
        │                    │───────────────────────►│
        │                    │◄───────────────────────│
        │                    │                        │
        │                    │ 5. Validate JWT locally│
        │                    │    - signature OK      │
        │                    │    - not expired        │
        │                    │    - issuer matches     │
        │                    │                        │
        │ 6. 200 OK + data   │                        │
        │◄───────────────────│                        │

  Communication Patterns
  ══════════════════════
  REST:      Client ──► Server ──► Client  (request-response)
  SSE:       Client ◄── Server             (one-way stream)
  WebSocket: Client ◄──► Server            (bidirectional)
  GraphQL:   Client ──► Server ──► Client  (client picks fields)

Key Takeaway

Modern Spring supports multiple communication patterns, each serving a different need. OAuth2 Resource Server validates JWT tokens from an external authorization server like Keycloak, automatically fetching JWKS keys and verifying signature, expiry, and issuer claims. The @HttpExchange annotation in Spring 6 generates declarative HTTP clients from interfaces, replacing Feign with a Spring-native solution backed by RestClient. WebFlux provides reactive Mono and Flux types for non-blocking I/O on Netty's event loop.

At National Bank of India, REST handles account CRUD with OAuth2 JWT security, SSE streams real-time NIFTY50 prices that browsers auto-reconnect to, WebSocket provides bidirectional communication for Rajesh's trading terminal, and GraphQL lets the mobile app request only the balance field while the web app gets the full account details. Each pattern serves a different use case: REST for standard CRUD, SSE for one-way server push, WebSocket for bidirectional real-time, and GraphQL for flexible queries that eliminate over-fetching.

Remember one sentence: OAuth2 Resource Server validates external JWTs, @HttpExchange generates type-safe clients, WebFlux streams with Mono and Flux, and GraphQL lets clients pick exactly the fields they need.

---

Share
Use ← → arrow keys to navigate