Tech stack: Spring Boot 3 · Spring Cloud Alibaba · OpenFeign · JWT · Gateway · Nacos


How to use this document

When you talk about a project in an interview, the worst thing you can do is start from code details. The right order is:

First explain what problem it solves → then the overall architecture → then walk through it by request flow → finally handle the deep-dive questions.

This document is organized in that order. The quote boxes marked with 🎤 are lines you can essentially read out as-is.


1. One-sentence overview (for the opening)

🎤 I built a microservice auth system based on Spring Cloud Alibaba. It splits “authentication” into three roles: the auth service issues tokens, the gateway uniformly verifies tokens, and each business service gets the user’s identity from a request header. Overall it uses JWT for stateless auth, services communicate via OpenFeign, and Nacos handles service discovery.

Keyword anchors (interviewers will follow up on these, so be prepared): stateless JWT, unified gateway auth, OpenFeign remote calls, Nacos service discovery, BCrypt password hashing, separation of concerns.


2. What problem it solves (the motivation)

Background: the system is a microservice architecture with several independent services (auth, user-service, archive-service, etc.). If each service did its own login verification, there’d be two problems:

  • Duplication: every service writes the same auth logic, and changing a rule means changing many places.
  • Stateful and hard to scale: traditional sessions live in a single server’s memory, aren’t shared across instances, and make scaling out hard.

The approach: “unified gateway security check + stateless JWT tokens.” All requests first pass through the gateway for token verification; business services no longer re-authenticate. The token itself is self-contained with user info, and verification only needs a local signature check with a key, without relying on server-side storage.

🎤 An airport analogy: the gateway is the security checkpoint, strictly checking your passport once (signature verification); once you pass, you get a wristband with your identity on it (the user id in the request header); the later boarding gates and duty-free shops (the business services) only look at the wristband and don’t re-check the passport. Verification happens once, and business services carry zero burden.


3. Overall architecture (the skeleton)

Module breakdown: the project is a Maven multi-module microservice. The core modules and their responsibilities:

Module Type Responsibility
gateway Gateway Entry point for all requests; uniformly verifies JWT; routes to backend services
auth Auth service Login, registration; verifies passwords; issues JWTs. Doesn’t connect to a database itself
user-service Business service Manages user data (CRUD on the tb_user table)
archive-service Business service Archive business; reads the current user’s identity from the request header
api Contract module Holds each service’s Feign interface declarations + accompanying DTOs (depended on by all services)
common Base infrastructure Unified Result response, global config, ThreadLocal user context, etc.

A key design — separating auth from user-service: auth only handles the “authentication action” (verifying passwords, issuing tokens) and doesn’t touch the database; when it needs user data, it makes a remote call to user-service via OpenFeign. user-service only handles “user data” and stays out of login logic.

🎤 Why not merge them? Because the responsibilities differ: auth handles “the act of verifying identity,” user-service handles “how user data is stored and retrieved.” Split apart, auth can focus on auth and user-service can focus on data, without polluting each other. That’s also why auth has no database dependency at all.


4. The three core flows (the main interview thread — tell it this way)

The most effective way to present a project is to “walk through it by request flow.” The three flows: registration, login, and accessing business with a token.

4.1 Registration flow

  1. The user submits a phone number + password to the auth service.
  2. Duplicate check: auth calls user-service via OpenFeign to confirm the phone number isn’t already registered.
  3. Hashing: auth uses passwordEncoder.encode() to BCrypt-hash the plaintext password into ciphertext.
  4. Storage: auth passes the ciphertext to user-service via OpenFeign, which stores it in the tb_user table.

Design point: password hashing happens in auth; user-service receives ciphertext and is only responsible for storing it, not for hashing — hashing is auth logic, storage is data logic.

🎤 Passwords are never stored in plaintext. BCrypt is a one-way hash — like grinding meat into mince: you can hash but can’t reverse it. Even if the database leaks, the attacker can’t get the original passwords. BCrypt also has a built-in random salt, so the same password hashes differently each time, effectively defending against rainbow tables.

4.2 Login flow

  1. The user submits a phone number + password.
  2. Look up the user: auth calls user-service by phone number via OpenFeign and gets the user info including the ciphertext password.
  3. Verify the password: compare using passwordEncoder.matches(plaintext, ciphertext).
  4. Issue a JWT: on success, use jjwt to put the user id into the token payload, sign it with the key, and return the token.

The core insight about verifying passwords: it’s not “decrypt the database ciphertext and compare” (hashes are irreversible), but hash the plaintext the user just entered again and compare whether the two ciphertexts match. This logic is wrapped up by matches().

🎤 A JWT has three parts: header (algorithm), payload (user id, expiry), and signature (anti-forgery). The payload is Base64-encoded and visible in plaintext, so only put “harmless if seen” info like the user id in it — never the password. Anti-forgery relies on the third part, the signature — generated with a key only the server knows, so even if someone alters the payload they can’t compute the correct signature.

4.3 The flow for accessing business with a token

  1. The frontend carries the token: after login it stores the token, then puts it in the Authorization header on every request.
  2. The gateway verifies: the request first reaches the gateway, the GlobalFilter intercepts it and does three things: check the whitelist → take the token → verify the signature + check expiry.
  3. Inject the identity: on success, the gateway parses the user id out of the token and writes it into a custom X-User-Id header.
  4. Route and forward: by the routing rules (lb://service-name) it finds the target service via Nacos and forwards to it.
  5. Business reads the identity: the business service uses an interceptor to take the user id out of X-User-Id, stores it in a ThreadLocal, and business code anywhere uses UserContext.getUserId() to get it.

🎤 The whitelist is key: the login and registration endpoints themselves can’t require a token — the user hasn’t logged in yet, so where would the token come from? Otherwise you get a deadlock. So these two endpoints are let through directly at the gateway.


5. Key technical points (handling deep dives — these are the most likely follow-ups)

5.1 Why is JWT verification at the gateway, not in every service?

  • Performance: verification happens once at the gateway; business services carry zero auth burden.
  • Decoupling: business services don’t need to understand JWT or hold the key; the code is minimal — they only read a request header.
  • The advantage of JWT being self-contained: verification is a local computation with the key — no database lookup, no remote call — so it’s fast; this is also JWT’s core advantage over sessions in a distributed setting: stateless and horizontally scalable.

5.2 Why does the gateway use WebFlux (reactive)?

The gateway uses Spring Cloud Gateway, which is built on the non-blocking WebFlux model, unlike ordinary Servlet services (auth/user-service).

  • Servlet (blocking): one request occupies one thread; while waiting on IO the thread idles and wastes capacity, and under high concurrency threads are easily exhausted.
  • WebFlux (non-blocking): threads don’t idle; while waiting on IO they serve other requests, so a small number of threads handles massive concurrency.
  • Why it suits a gateway: the gateway has the most traffic and mostly “waits to forward to the backend,” which fits non-blocking perfectly; the cost is you can’t write blocking code inside the gateway, and JWT verification is a pure local computation that’s inherently non-blocking.

5.3 Why pass the user identity via a request header instead of using ThreadLocal at the gateway?

The core reason: the gateway and the business service are two separate processes, each with its own memory. ThreadLocal is only valid within a process; what the gateway stores can’t cross over to the business service, which can’t read it.

  • Crossing processes requires a network-transportable carrier: a request header travels over the network with the HTTP request, so it can “fly” from the gateway to the business service.
  • The two ThreadLocals are separate: the gateway parses the id → writes the header (the cross-process bridge) → the business service reads the header → stores it in its own process’s ThreadLocal for local use.

🎤 This is a fundamental principle of distributed systems: passing data across services can only use a “network-transportable carrier” (request headers, request bodies, message queues), not “in-process memory” (ThreadLocal, static variables, singletons) — in-process things are trapped inside their own process.

5.4 Why must ThreadLocal be removed?

Thread reuse mixes up data: web server threads are reused from a thread pool. After a thread finishes handling user A’s request it isn’t destroyed but is recycled to handle user B’s. If you don’t clear the ThreadLocal, B may read A’s leftover user id, causing a serious “mistaken identity” bug.

The practice: call removeUser() in the interceptor’s afterCompletion (which runs when the request fully ends, even on exception).

5.5 A security risk (raise it yourself — it’s a bonus point)

The business service trusts the X-User-Id header. If someone bypasses the gateway and hits the business service directly, forging this header, they can impersonate others.

The fix: ensure the business service can only be accessed through the gateway and doesn’t expose its port directly (network isolation + internal auth), so every request must pass the gateway’s security check for X-User-Id to be trustworthy.


6. Tech stack at a glance

Domain Technology choice Purpose
Microservice framework Spring Boot 3 + Spring Cloud Alibaba 2023 Base framework
Service discovery Nacos Service registration & discovery; services find each other by name
Service calls OpenFeign + LoadBalancer Declarative remote calls + load balancing
Gateway Spring Cloud Gateway (WebFlux) Unified entry, auth, routing
Authentication JWT (jjwt) Issuing and verifying stateless tokens
Passwords BCrypt (Spring Security Crypto) One-way password hashing
Persistence MyBatis-Plus + PostgreSQL User data storage
Config management Nacos Config Centralized config (keys, etc. can go in shared config)

7. The complete data flow (one diagram makes it clear)

Login to get a token:

Frontend → Gateway (whitelist pass-through) → auth → Feign query user-service → verify password → issue JWT → return token

Access with a token:

Frontend (with token)
  → Gateway GlobalFilter (verify signature + check expiry)
  → parse userId, inject into X-User-Id
  → route (lb://) find service via Nacos
  → business service interceptor reads header, stores in ThreadLocal
  → business code UserContext.getUserId()

8. Interview self-check list (if you can answer these, you truly understand it)

  • Why can’t passwords be stored in plaintext? Which two properties of BCrypt make it secure?
  • What are the three parts of a JWT? Why can’t the payload hold the password? What provides anti-forgery?
  • Is login password verification “decrypt and compare” or “re-hash and compare”? Why?
  • Why doesn’t auth connect to the database directly? How does it get user data?
  • Why is verifying the signature once at the gateway enough? JWT’s advantages over sessions?
  • Why does the gateway use WebFlux? How does it differ from the Servlet model?
  • Why pass the user identity via a request header instead of storing it in a ThreadLocal at the gateway?
  • Why must ThreadLocal be removed? What happens if you don’t?
  • What security risk does X-User-Id have? How do you guard against it?
  • What’s the difference in positioning between the api module and the common module? Why do the Feign interfaces go in api?

Tip: when presenting the project, first cover “what problem it solves” and “the overall architecture,” then walk through the three flows, and adapt to deep-dive questions on the fly. Never start by pasting code.