feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
package com.gcsc.guide.auth;
|
|
|
|
|
|
|
|
|
|
import com.gcsc.guide.dto.AuthResponse;
|
|
|
|
|
import com.gcsc.guide.dto.GoogleLoginRequest;
|
|
|
|
|
import com.gcsc.guide.dto.UserResponse;
|
|
|
|
|
import com.gcsc.guide.entity.User;
|
|
|
|
|
import com.gcsc.guide.repository.UserRepository;
|
feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00
|
|
|
import com.gcsc.guide.service.ActivityService;
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
|
2026-02-14 21:30:48 +09:00
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
|
|
import io.swagger.v3.oas.annotations.media.Content;
|
|
|
|
|
import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
|
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
|
|
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|
|
|
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
|
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
import jakarta.validation.Valid;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
import org.springframework.web.server.ResponseStatusException;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
@RestController
|
|
|
|
|
@RequestMapping("/api/auth")
|
|
|
|
|
@RequiredArgsConstructor
|
2026-02-14 21:30:48 +09:00
|
|
|
@Tag(name = "01. 인증", description = "Google OAuth2 로그인 및 JWT 토큰 관리")
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
public class AuthController {
|
|
|
|
|
|
|
|
|
|
private static final String AUTO_ADMIN_EMAIL = "htlee@gcsc.co.kr";
|
|
|
|
|
|
|
|
|
|
private final GoogleTokenVerifier googleTokenVerifier;
|
|
|
|
|
private final JwtTokenProvider jwtTokenProvider;
|
|
|
|
|
private final UserRepository userRepository;
|
feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00
|
|
|
private final ActivityService activityService;
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
|
2026-02-14 21:30:48 +09:00
|
|
|
@Operation(summary = "Google 로그인",
|
|
|
|
|
description = "Google ID Token을 검증하고 JWT를 발급합니다. "
|
|
|
|
|
+ "신규 사용자는 PENDING 상태로 생성되며, htlee@gcsc.co.kr은 자동 ACTIVE + 관리자 부여됩니다.",
|
|
|
|
|
security = {})
|
|
|
|
|
@ApiResponses({
|
|
|
|
|
@ApiResponse(responseCode = "200", description = "로그인 성공, JWT 토큰 발급",
|
|
|
|
|
content = @Content(schema = @Schema(implementation = AuthResponse.class))),
|
|
|
|
|
@ApiResponse(responseCode = "401", description = "유효하지 않은 Google 토큰 또는 허용되지 않은 이메일 도메인",
|
|
|
|
|
content = @Content)
|
|
|
|
|
})
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
@PostMapping("/google")
|
feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00
|
|
|
public ResponseEntity<AuthResponse> googleLogin(
|
|
|
|
|
@Valid @RequestBody GoogleLoginRequest request,
|
|
|
|
|
HttpServletRequest httpRequest) {
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
GoogleIdToken.Payload payload = googleTokenVerifier.verify(request.idToken());
|
|
|
|
|
if (payload == null) {
|
|
|
|
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "유효하지 않은 Google 토큰입니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String email = payload.getEmail();
|
|
|
|
|
String name = (String) payload.get("name");
|
|
|
|
|
String avatarUrl = (String) payload.get("picture");
|
|
|
|
|
|
|
|
|
|
userRepository.findByEmail(email)
|
|
|
|
|
.ifPresentOrElse(
|
|
|
|
|
existingUser -> {
|
|
|
|
|
existingUser.updateProfile(name, avatarUrl);
|
|
|
|
|
existingUser.updateLastLogin();
|
|
|
|
|
userRepository.save(existingUser);
|
|
|
|
|
},
|
|
|
|
|
() -> createNewUser(email, name, avatarUrl)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
User userWithRoles = userRepository.findByEmailWithRoles(email)
|
|
|
|
|
.orElseThrow();
|
|
|
|
|
|
feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00
|
|
|
activityService.recordLogin(
|
|
|
|
|
userWithRoles.getId(),
|
|
|
|
|
httpRequest.getRemoteAddr(),
|
|
|
|
|
httpRequest.getHeader("User-Agent"));
|
|
|
|
|
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
String token = jwtTokenProvider.generateToken(
|
|
|
|
|
userWithRoles.getId(), userWithRoles.getEmail(), userWithRoles.isAdmin());
|
|
|
|
|
|
|
|
|
|
return ResponseEntity.ok(new AuthResponse(token, UserResponse.from(userWithRoles)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:30:48 +09:00
|
|
|
@Operation(summary = "현재 사용자 정보 조회",
|
|
|
|
|
description = "JWT 토큰으로 인증된 현재 사용자의 상세 정보와 롤 목록을 반환합니다.")
|
|
|
|
|
@ApiResponses({
|
|
|
|
|
@ApiResponse(responseCode = "200", description = "사용자 정보 조회 성공"),
|
|
|
|
|
@ApiResponse(responseCode = "401", description = "인증 실패 (토큰 없음/만료)", content = @Content),
|
|
|
|
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
|
|
|
|
})
|
|
|
|
|
@SecurityRequirement(name = "Bearer JWT")
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
@GetMapping("/me")
|
|
|
|
|
public ResponseEntity<UserResponse> getCurrentUser(Authentication authentication) {
|
|
|
|
|
Long userId = (Long) authentication.getPrincipal();
|
|
|
|
|
|
|
|
|
|
User user = userRepository.findByIdWithRoles(userId)
|
|
|
|
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다"));
|
|
|
|
|
|
|
|
|
|
return ResponseEntity.ok(UserResponse.from(user));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:30:48 +09:00
|
|
|
@Operation(summary = "로그아웃",
|
|
|
|
|
description = "Stateless JWT 방식이므로 서버 측 처리 없이 204를 반환합니다. 클라이언트에서 토큰을 삭제하세요.")
|
|
|
|
|
@ApiResponse(responseCode = "204", description = "로그아웃 성공")
|
|
|
|
|
@SecurityRequirement(name = "Bearer JWT")
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
@PostMapping("/logout")
|
|
|
|
|
public ResponseEntity<Void> logout() {
|
|
|
|
|
return ResponseEntity.noContent().build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private User createNewUser(String email, String name, String avatarUrl) {
|
|
|
|
|
User newUser = new User(email, name, avatarUrl);
|
|
|
|
|
|
|
|
|
|
if (AUTO_ADMIN_EMAIL.equals(email)) {
|
|
|
|
|
newUser.activate();
|
|
|
|
|
newUser.grantAdmin();
|
|
|
|
|
log.info("관리자 자동 승인: {}", email);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newUser.updateLastLogin();
|
|
|
|
|
return userRepository.save(newUser);
|
|
|
|
|
}
|
|
|
|
|
}
|