feat: JWT RS256 + refresh 会话轮换与 /api/v1 契约落地(ADR-003)
- 会话逻辑下沉 patbond-user(新 /internal/sessions;auth_sessions 只存 SHA-256 摘要,刷新即轮换并链 token_family,重用撤销整个 family,退出仅撤当前会话,多设备并行) - patbond-auth 作薄入口签发 RS256 JWT(access 15m / refresh 30d 均为配置项;密钥经环境变量注入,仓库零密钥材料,测试密钥运行时生成);公开端点迁至 /api/v1,冻结契约字段零偏差,expiresAt 无时区遗留修复 - /internal/** 加 X-Internal-Token 服务间鉴权(无凭证 401);/api/v1/me 由 user 以公钥本地验签(40101/40102 新错误码) - 登录失败限制:按用户名 15 分钟窗口 5 次锁 15 分钟(423/42300,DB 原子计数,可配置) - 修复两处存量缺陷:ErrorDecoder 未注册进 Feign 子上下文、JDK HttpURLConnection 对流式 POST 的 401 读不到错误体(引入 feign-hc5)——真实调用中下游错误码此前一律折叠为 503 - 门禁:JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test → BUILD SUCCESS,73 测试 0 失败(37→73),含同 JVM 双服务真实 HTTP E2E:注册→me→刷新→旧 refresh 重用被拒且 family 撤销→退出后 refresh 失效;Testcontainers postgres:18,无遗留容器 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ Patbond API is a Spring Boot multi-module backend.
|
||||
- Java 17 (build baseline; use JDK 17 for release builds)
|
||||
- Spring Boot 3.5.16
|
||||
- Spring Cloud 2025.0.3 (OpenFeign only)
|
||||
- PostgreSQL 16 + Flyway (patbond-user owns the `identity`/`media` schemas)
|
||||
- PostgreSQL 18 + Flyway (patbond-user owns the `identity`/`media` schemas)
|
||||
- Maven (use the committed Maven Wrapper `./mvnw`)
|
||||
|
||||
## Build and Test
|
||||
@@ -22,7 +22,7 @@ Patbond API is a Spring Boot multi-module backend.
|
||||
./mvnw clean test
|
||||
```
|
||||
|
||||
Integration tests start a disposable `postgres:16` via Testcontainers, so a
|
||||
Integration tests start a disposable `postgres:18` via Testcontainers, so a
|
||||
running Docker daemon is required (no local PostgreSQL installation or
|
||||
credentials are needed).
|
||||
|
||||
@@ -34,7 +34,7 @@ JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test
|
||||
|
||||
## Run
|
||||
|
||||
Running `patbond-user` requires a reachable PostgreSQL 16 database; Flyway
|
||||
Running `patbond-user` requires a reachable PostgreSQL 18 database; Flyway
|
||||
applies the versioned migrations in
|
||||
`patbond-user/src/main/resources/db/migration` automatically on startup.
|
||||
Development seed data (`db/dev`, regions reference rows) is opt-in via a dev
|
||||
@@ -59,13 +59,24 @@ Install the shared module once, then run each service in its own terminal:
|
||||
./mvnw -pl patbond-auth spring-boot:run
|
||||
```
|
||||
|
||||
Running the services also needs an RS256 key pair for access tokens (the
|
||||
private key signs in `patbond-auth`, the public key verifies in
|
||||
`patbond-user`; neither is committed):
|
||||
|
||||
```bash
|
||||
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt-private.pem
|
||||
openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
|
||||
export PATBOND_JWT_PRIVATE_KEY=$PWD/jwt-private.pem # patbond-auth
|
||||
export PATBOND_JWT_PUBLIC_KEY=$PWD/jwt-public.pem # patbond-user
|
||||
```
|
||||
|
||||
Tests do not require this copy step — `patbond-auth/src/test/resources/application.yml`
|
||||
keeps `./mvnw clean test` self-contained on a clean checkout.
|
||||
|
||||
Smoke check:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8081/auth/register \
|
||||
curl -X POST http://127.0.0.1:8081/api/v1/auth/register \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"demo_user","password":"secret123"}'
|
||||
```
|
||||
@@ -80,6 +91,14 @@ curl -X POST http://127.0.0.1:8081/auth/register \
|
||||
| `PATBOND_DB_URL` | `jdbc:postgresql://127.0.0.1:5432/patbond` | patbond-user |
|
||||
| `PATBOND_DB_USER` | `patbond` | patbond-user |
|
||||
| `PATBOND_DB_PASSWORD` | `patbond` | patbond-user |
|
||||
| `PATBOND_INTERNAL_TOKEN` | `dev-only-internal-token` | both (shared secret for `/internal/**`; inject a strong random value outside local dev) |
|
||||
| `PATBOND_JWT_PRIVATE_KEY` | _(none, required)_ | patbond-auth (RS256 private key: PEM path or inline PEM) |
|
||||
| `PATBOND_JWT_PUBLIC_KEY` | _(none)_ | patbond-user (RS256 public key: PEM path or inline PEM) |
|
||||
| `PATBOND_ACCESS_TTL` | `15m` | patbond-auth (access token lifetime, ADR-003) |
|
||||
| `PATBOND_REFRESH_TTL` | `30d` | patbond-user (refresh token lifetime, ADR-003) |
|
||||
| `PATBOND_LOGIN_LOCK_MAX_FAILURES` | `5` | patbond-user (login failures before lockout) |
|
||||
| `PATBOND_LOGIN_LOCK_WINDOW` | `15m` | patbond-user (failure counting window) |
|
||||
| `PATBOND_LOGIN_LOCK_DURATION` | `15m` | patbond-user (lock duration) |
|
||||
|
||||
Machine-specific values live in the git-ignored `application.yml` (copied from the
|
||||
committed `.sample`); never commit secrets to the samples.
|
||||
@@ -90,21 +109,27 @@ committed `.sample`); never commit secrets to the samples.
|
||||
|
||||
- Application name: `patbond-auth`
|
||||
- Default port: `8081`
|
||||
- Register: `POST /auth/register`
|
||||
- Login: `POST /auth/login`
|
||||
- Register: `POST /api/v1/auth/register`
|
||||
- Login: `POST /api/v1/auth/login`
|
||||
- Refresh (rotates the refresh token): `POST /api/v1/auth/refresh`
|
||||
- Logout (revokes the current session): `POST /api/v1/auth/logout`
|
||||
|
||||
### User Service
|
||||
|
||||
- Application name: `patbond-user`
|
||||
- Default port: `8082`
|
||||
- Create user: `POST /internal/users`
|
||||
- Verify password: `POST /internal/users/verify-password`
|
||||
- Get user by id: `GET /internal/users/{id}`
|
||||
- Get user by username: `GET /internal/users/by-username/{username}`
|
||||
- Current user profile: `GET /api/v1/me` (Bearer access token, verified locally with the RS256 public key)
|
||||
- Internal (require `X-Internal-Token`):
|
||||
- Create user: `POST /internal/users`
|
||||
- Verify password: `POST /internal/users/verify-password`
|
||||
- Get user by id: `GET /internal/users/{id}`
|
||||
- Get user by username: `GET /internal/users/by-username/{username}`
|
||||
- Sessions: `POST /internal/sessions`, `POST /internal/sessions/refresh`, `POST /internal/sessions/revoke`
|
||||
|
||||
> Note: user data is persisted in PostgreSQL (`identity.users` /
|
||||
> `identity.user_credentials`, bcrypt password hashes, UUIDv7 ids generated in
|
||||
> the application). Errors follow the `{code, message, data}` envelope with
|
||||
> stable business codes and matching HTTP statuses. Verifiable JWT tokens,
|
||||
> refresh sessions, and `/internal` access control are planned in iteration 1
|
||||
> follow-up tasks.
|
||||
> the application). Refresh sessions live in `identity.auth_sessions` (SHA-256
|
||||
> digests only, rotation on every refresh, token-family revocation on reuse,
|
||||
> ADR-003). Errors follow the `{code, message, data}` envelope with stable
|
||||
> business codes and matching HTTP statuses; the public contract is documented
|
||||
> in `patbond-doc/docs/api/openapi.yaml`.
|
||||
|
||||
Reference in New Issue
Block a user