이 문서는 JWT 인증이 적용된 상태에서 새로운 API 엔드포인트를 작성할 때 참고하는 예시 문서입니다.
개발용 테스트 계정이 필요하면 먼저 ./scripts/test-data.sh를 실행하세요.
- SecurityConfig에서
anyRequest().authenticated()로 설정되어 있음 - permitAll 목록에 없는 모든 엔드포인트는 자동으로 JWT 검증됨
- 프론트는 매 요청마다 헤더에
Authorization: Bearer {accessToken}을 넣어서 보냄 - 별도 검증 코드 불필요 — Spring Security가 자동 처리
/api/user/register
/api/user/login
/api/user/exist/**
/api/auth/reissue
/api/user/find-email
/api/user/verify-identity
/api/user/reset-password
/api/system/health
/swagger-ui/**
/v3/api-docs/**
/images/**
위 경로 외의 모든 요청은 JWT 인증 필수입니다.
유저 정보가 필요하고, 요청 데이터도 있는 경우.
@PostMapping("/register")
public StoreResponse register(@RequestBody @Valid StoreRequest request,
@AuthenticationPrincipal Jwt jwt) {
Long userId = Long.parseLong(jwt.getSubject());
return storeService.register(userId, request);
}프론트 (Flutter/Dio):
await dio.post('/api/store/register',
data: {'name': '오늘의빵', 'address': '서울시 강남구'},
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
);유저 정보만 필요하고, 별도 데이터는 없는 경우.
@PostMapping("/logout")
public LogoutResponse logout(@AuthenticationPrincipal Jwt jwt) {
Long userId = Long.parseLong(jwt.getSubject());
authService.logout(userId);
return LogoutResponse.ok();
}프론트:
await dio.post('/api/auth/logout',
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
);특정 리소스를 조회/수정할 때.
@GetMapping("/{storeId}")
public StoreResponse getStore(@PathVariable Long storeId,
@AuthenticationPrincipal Jwt jwt) {
Long userId = Long.parseLong(jwt.getSubject());
return storeService.getStore(userId, storeId);
}프론트:
await dio.get('/api/store/1',
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
);로그인, 회원가입 등 인증 없이 접근 가능한 API.
@AuthenticationPrincipal 사용하지 않음.
@PostMapping("/login")
public UserLoginResponse login(@RequestBody @Valid UserLoginRequest request) {
return userService.login(request);
}프론트:
final response = await dio.post('/api/user/login',
data: {'email': 'demo-user@todaybread.local', 'password': 'todaybread123'},
);
// 응답에서 accessToken, refreshToken 저장새로운 permitAll 경로를 추가하려면
SecurityConfig.java의requestMatchers()에 추가해야 합니다.
@AuthenticationPrincipal Jwt jwt로 받은 뒤:
| 메서드 | 반환값 | 설명 |
|---|---|---|
jwt.getSubject() |
"1" (String) |
userId (Long으로 파싱 필요) |
jwt.getClaim("email") |
"user@test.com" |
유저 이메일 |
jwt.getClaim("role") |
"USER" 또는 "BOSS" |
유저 역할 |
JWT 검증 실패 시 Spring Security가 자동으로 에러 응답을 보냅니다:
| 상황 | 에러 코드 | HTTP | 메시지 |
|---|---|---|---|
| 토큰 없음 | AUTH_002 | 401 | 유효하지 않은 Access 토큰입니다. |
| 토큰 만료 | AUTH_001 | 401 | Access 토큰이 만료되었습니다. |
| 토큰 위조/형식 오류 | AUTH_002 | 401 | 유효하지 않은 Access 토큰입니다. |
프론트에서 401 받으면:
- AUTH_001 (만료) → refreshToken으로
/api/auth/reissue호출 - reissue 성공 → 새 토큰 저장 후 원래 요청 재시도
- reissue 실패 (refreshToken도 만료) → 로그인 화면으로 이동
프론트 요청 → 401 (AUTH_001 만료)
↓
POST /api/auth/reissue (body: { refreshToken: "..." })
↓
성공 → 새 accessToken + refreshToken 받음 → 원래 요청 재시도
실패 → 로그인 화면으로 이동