# 13.Swagger API Documentation & JWT Testing

### 13.1. Mục tiêu chương

Chương này quy định cách:

- Tạo tài liệu API bằng Swagger/OpenAPI
- Cấu hình Swagger cho Spring Boot
- Tích hợp xác thực JWT vào Swagger
- Test API có auth trực tiếp trên Swagger UI

Mục tiêu:

- Dev và QA có thể test API nhanh
- Chuẩn hóa tài liệu API
- Tránh test sai do thiếu token
- Đồng nhất cách dùng Swagger giữa các team

---

### 13.2. Truy cập Swagger UI

Sau khi chạy service:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui/index.html`</div></div>Hoặc:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui.html`</div></div>Swagger hiển thị danh sách API theo controller.

---

### 13.3. Cấu hình Swagger trong Spring Boot

Dependency Maven:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%3Cdependency%3E-%3Cgroupi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-tag"><<span class="hljs-name">dependency</span></span>>    <span class="hljs-tag"><<span class="hljs-name">groupId</span></span>>org.springdoc<span class="hljs-tag"></<span class="hljs-name">groupId</span></span>>    <span class="hljs-tag"><<span class="hljs-name">artifactId</span></span>>springdoc-openapi-starter-webmvc-ui<span class="hljs-tag"></<span class="hljs-name">artifactId</span></span>>    <span class="hljs-tag"><<span class="hljs-name">version</span></span>>2.7.0<span class="hljs-tag"></<span class="hljs-name">version</span></span>><span class="hljs-tag"></<span class="hljs-name">dependency</span></span>>`</div></div>---

#### 13.3.1. Cấu hình OpenAPI + JWT

File:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-config%2Fopenapiconfig"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`config/OpenApiConfig.java`</div></div><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40configuration-publi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Configuration</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OpenApiConfig</span> {    <span class="hljs-meta">@Bean</span>    <span class="hljs-keyword">public</span> OpenAPI <span class="hljs-title function_">api</span><span class="hljs-params">()</span> {        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OpenAPI</span>()                .info(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Info</span>()                        .title(<span class="hljs-string">"Company Backend API"</span>)                        .version(<span class="hljs-string">"v1.0"</span>)                        .description(<span class="hljs-string">"API documentation"</span>))                .components(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Components</span>()                        .addSecuritySchemes(<span class="hljs-string">"bearerAuth"</span>,                                <span class="hljs-keyword">new</span> <span class="hljs-title class_">SecurityScheme</span>()                                        .type(SecurityScheme.Type.HTTP)                                        .scheme(<span class="hljs-string">"bearer"</span>)                                        .bearerFormat(<span class="hljs-string">"JWT"</span>)                        )                )                .addSecurityItem(<span class="hljs-keyword">new</span> <span class="hljs-title class_">SecurityRequirement</span>().addList(<span class="hljs-string">"bearerAuth"</span>));    }}`</div></div>---

### 13.4. Đánh dấu API cần JWT trong Controller

Controller cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40restcontroller-%40req"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@RestController</span><span class="hljs-meta">@RequestMapping("/api/v1/users")</span><span class="hljs-meta">@SecurityRequirement(name = "bearerAuth")</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {`</div></div>Swagger sẽ hiển thị icon:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%F0%9F%94%92-get-%2Fapi%2Fv1%2Fusers"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`🔒 GET /api/v1/users`</div></div>---

### 13.5. Cấu hình Spring Security cho Swagger

Swagger UI phải được phép truy cập không cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.requestmatchers%28-%22%2F"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`.requestMatchers(    <span class="hljs-string">"/swagger-ui/**"</span>,    <span class="hljs-string">"/swagger-ui.html"</span>,    <span class="hljs-string">"/v3/api-docs/**"</span>).permitAll()`</div></div>API business:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.anyrequest%28%29.authen"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-selector-class">.anyRequest</span>()<span class="hljs-selector-class">.authenticated</span>()`</div></div>---

### 13.6. Test API có JWT trên Swagger (Step-by-Step)

#### Bước 1. Login lấy JWT

Gọi API login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-post-%2Fapi%2Fv1%2Fauth%2Flo"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`POST /api/v1/auth/login`</div></div>Response:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%7B-%22token%22%3A-%22eyjhbgci"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-punctuation">{</span>  <span class="hljs-attr">"token"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."</span><span class="hljs-punctuation">}</span>`</div></div>---

#### Bước 2. Mở Swagger

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808-2"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui/index.html`</div></div>---

#### Bước 3. Click Authorize

Góc phải Swagger:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorize-%F0%9F%94%92"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Authorize</span> 🔒`</div></div>Click vào.

---

#### Bước 4. Nhập JWT

Nhập:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-bearer-eyjhbgcioijiu"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Bearer</span> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`</div></div>Lưu ý:

- Phải có chữ **Bearer**
- Có khoảng trắng sau Bearer

Ví dụ đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-bearer-eyjhbgcioijiu-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Bearer</span> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`</div></div>---

#### Bước 5. Authorize

Click:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorize"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Authorize</span>`</div></div>Swagger sẽ lưu token cho toàn bộ API.

---

#### Bước 6. Test API

Chọn API có 🔒:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-get-%2Fapi%2Fv1%2Ftheme-co"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`GET /api/v1/theme-config`</div></div>Click:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-execute"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">Execute</span>`</div></div>Swagger gửi request:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorization%3A-beare"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-section">Authorization: Bearer eyJhbGciOiJIUzI1Ni...</span>`</div></div>---

### 13.7. Kiểm tra Swagger đã gửi token chưa

Trong Swagger phần Curl phải có:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk--h-%22authorization%3A-b"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`-H <span class="hljs-string">"Authorization: Bearer eyJhbGciOiJIUzI1Ni..."</span>`</div></div>Nếu không có → chưa Authorize.

---

### 13.8. Lỗi thường gặp

#### 403 Forbidden

Nguyên nhân:

- Chưa nhập token
- Token sai
- Token hết hạn
- API cần auth nhưng Swagger chưa Authorize

---

#### Không thấy nút Authorize

Nguyên nhân:

- Chưa cấu hình SecurityScheme
- Swagger config sai
- Cache browser

Khắc phục:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-ctrl-%2B-f5"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Ctrl</span> + F5`</div></div>---

#### Swagger call được API không cần token

Nguyên nhân:

Security config permitAll API.

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.requestmatchers%28%22%2Fa"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-selector-class">.requestMatchers</span>("/api/v1/**")<span class="hljs-selector-class">.permitAll</span>()`</div></div>Swagger không cần JWT.

---

### 13.9. Quy định chuẩn sử dụng Swagger trong dự án

Controller cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40securityrequirement"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@SecurityRequirement(name = <span class="hljs-string">"bearerAuth"</span></span>)`</div></div>Controller public:

không cần annotation.

---

API test phải:

- Login lấy JWT
- Authorize Swagger
- Test API

---

### 13.10. Quy định commit &amp; PR

API mới phải:

- Hiển thị trên Swagger
- Có request/response schema
- Có SecurityRequirement nếu cần auth
- Test được trên Swagger

PR bị reject nếu:

- API không có Swagger
- Swagger lỗi
- Không test được

---

### 13.11. Checklist Swagger cho Dev

Trước khi merge:

- Swagger mở được
- API hiển thị
- Authorize hoạt động
- JWT call OK
- Response đúng schema

---

### 13.12. Best Practice

Không permitAll API production.

Swagger chỉ permit:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%2Fswagger-ui%2F%2A%2A-%2Fv3%2Fa"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`/swagger-ui/**/v3/api-docs/**`</div></div>JWT luôn test qua Authorize.