
OAuth 2.0은 사용자의 비밀번호를 공유하지 않고도 다른 서비스의 자원에 접근할 수 있도록 하는 권한 부여 프로토콜입니다.

src/main/java/com/onandhome/
├── auth/
│ ├── controller/
│ │ └── GoogleAuthController.java # 구글 로그인 API 엔드포인트
│ ├── service/
│ │ └── GoogleAuthService.java # 구글 OAuth 핵심 로직
│ └── dto/
│ ├── GoogleTokenResponse.java # 구글 토큰 응답 DTO
│ └── GoogleUserInfo.java # 구글 사용자 정보 DTO
├── user/
│ ├── entity/User.java # 사용자 엔티티 (provider 필드)
│ └── UserRepository.java # 사용자 DB 조회
├── util/JWTUtil.java # JWT 토큰 생성/검증
└── SecurityConfig.java # Spring Security 설정
http://localhost:3000/auth/google/callback
# ============================================
# Google OAuth2 설정
# ============================================
# 환경변수로 관리
google.client-id=${GOOGLE_CLIENT_ID}
google.client-secret=${GOOGLE_CLIENT_SECRET}
# 프론트엔드 콜백 URL
google.redirect-uri=http://localhost:3000/auth/google/callback
# 구글 OAuth URL
google.auth-url=https://accounts.google.com/o/oauth2/v2/auth
google.token-url=https://oauth2.googleapis.com/token
google.user-info-url=https://www.googleapis.com/oauth2/v2/userinfo
구글 토큰 API 응답을 받는 DTO
@Data
public class GoogleTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("expires_in")
private Integer expiresIn;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("id_token")
private String idToken;
}
구글 사용자 정보 API 응답을 받는 DTO
@Data
public class GoogleUserInfo {
private String id; // 구글 고유 ID
private String email; // 이메일
private String name; // 이름
private String picture; // 프로필 사진
@JsonProperty("verified_email")
private Boolean verifiedEmail;
}
@Slf4j
@Service
@RequiredArgsConstructor
public class GoogleAuthService {
private final UserRepository userRepository;
private final RestTemplate restTemplate = new RestTemplate();
@Value("${google.client-id}")
private String clientId;
@Value("${google.client-secret}")
private String clientSecret;
@Value("${google.redirect-uri}")
private String redirectUri;
@Value("${google.token-url}")
private String tokenUrl;
@Value("${google.user-info-url}")
private String userInfoUrl;
// 구글 인증 코드로 액세스 토큰 받기
public GoogleTokenResponse getAccessToken(String code) {
log.info("=== 구글 액세스 토큰 요청 ===");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
params.add("redirect_uri", redirectUri);
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<GoogleTokenResponse> response = restTemplate.exchange(
tokenUrl,
HttpMethod.POST,
request,
GoogleTokenResponse.class
);
return response.getBody();
}
// 액세스 토큰으로 사용자 정보 받기
public GoogleUserInfo getUserInfo(String accessToken) {
log.info("=== 구글 사용자 정보 요청 ===");
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<GoogleUserInfo> response = restTemplate.exchange(
userInfoUrl,
HttpMethod.GET,
request,
GoogleUserInfo.class
);
return response.getBody();
}
// 구글 사용자 정보로 로그인 또는 회원가입 처리
public User processGoogleLogin(GoogleUserInfo googleUserInfo) {
log.info("=== 구글 로그인 처리 ===");
String provider = "GOOGLE";
String providerId = googleUserInfo.getId();
// 이미 가입된 사용자인지 확인
Optional<User> existingUser = userRepository
.findByProviderAndProviderId(provider, providerId);
if (existingUser.isPresent()) {
log.info("기존 구글 사용자 로그인: {}", providerId);
return existingUser.get();
}
// 신규 사용자 생성
log.info("신규 구글 사용자 회원가입: {}", providerId);
String name = googleUserInfo.getName() != null
? googleUserInfo.getName() : "구글사용자";
String email = googleUserInfo.getEmail();
// 이메일 중복 체크
if (email != null && userRepository.existsByEmail(email)) {
email = "google_" + providerId + "@google.user";
}
// userId 생성
String userId = "google_" + providerId;
if (userRepository.existsByUserId(userId)) {
userId = "google_" + providerId + "_" + UUID.randomUUID().toString().substring(0, 8);
}
// User 생성 및 저장
User newUser = User.builder()
.userId(userId)
.password(UUID.randomUUID().toString())
.username(name)
.email(email)
.provider(provider)
.providerId(providerId)
.role(1) // 일반 사용자
.active(true)
.build();
return userRepository.save(newUser);
}
}
getAccessToken(): 인증 코드 → 액세스 토큰getUserInfo(): 액세스 토큰 → 사용자 정보 processGoogleLogin(): 사용자 정보 → 회원가입/로그인@Tag(name = "구글 로그인")
@Slf4j
@RestController
@RequestMapping("/api/auth/google")
@RequiredArgsConstructor
public class GoogleAuthController {
private final GoogleAuthService googleAuthService;
private final JWTUtil jwtUtil;
@Value("${google.client-id}")
private String clientId;
@Value("${google.redirect-uri}")
private String redirectUri;
@Value("${google.auth-url}")
private String authUrl;
// 구글 로그인 URL 반환
@GetMapping("/login-url")
public ResponseEntity<Map<String, String>> getGoogleLoginUrl() {
log.info("=== 구글 로그인 URL 요청 ===");
String loginUrl = authUrl
+ "?client_id=" + clientId
+ "&redirect_uri=" + redirectUri
+ "&response_type=code"
+ "&scope=openid email profile";
Map<String, String> response = new HashMap<>();
response.put("loginUrl", loginUrl);
return ResponseEntity.ok(response);
}
// 구글 로그인 콜백 처리
@GetMapping("/callback")
public ResponseEntity<Map<String, Object>> googleCallback(
@RequestParam("code") String code,
HttpSession session) {
log.info("=== 구글 로그인 콜백 처리 시작 ===");
Map<String, Object> response = new HashMap<>();
try {
// 1. 액세스 토큰 받기
GoogleTokenResponse tokenResponse = googleAuthService.getAccessToken(code);
// 2. 사용자 정보 받기
GoogleUserInfo googleUserInfo = googleAuthService.getUserInfo(
tokenResponse.getAccessToken()
);
// 3. 로그인 처리
User user = googleAuthService.processGoogleLogin(googleUserInfo);
// 4. 세션에 저장
session.setAttribute("userId", user.getUserId());
session.setAttribute("username", user.getUsername());
session.setAttribute("role", user.getRole());
// 5. JWT 토큰 생성
Map<String, Object> claims = new HashMap<>();
claims.put("id", user.getId());
claims.put("userId", user.getUserId());
claims.put("role", user.getRole());
String accessToken = jwtUtil.generateToken(claims, 60); // 1시간
String refreshToken = jwtUtil.generateToken(claims, 60 * 24 * 7); // 7일
// 6. 응답 반환
response.put("success", true);
response.put("message", "구글 로그인 성공");
response.put("accessToken", accessToken);
response.put("refreshToken", refreshToken);
response.put("user", Map.of(
"id", user.getId(),
"userId", user.getUserId(),
"username", user.getUsername(),
"email", user.getEmail() != null ? user.getEmail() : "",
"role", user.getRole()
));
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("구글 로그인 실패", e);
response.put("success", false);
response.put("message", "구글 로그인 중 오류: " + e.getMessage());
return ResponseEntity.status(500).body(response);
}
}
}
/api/auth/google/login-url → 구글 로그인 URL 생성/api/auth/google/callback?code=xxx → 인증 코드 처리 및 JWT 발급@Entity
@Table(name = "users")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String userId;
private String password;
private String username;
private String email;
// 소셜 로그인 필드
private String provider; // "GOOGLE", "KAKAO", "NAVER"
private String providerId; // 소셜 플랫폼의 고유 ID
private Integer role; // 0: 관리자, 1: 일반사용자
private Boolean active;
}
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUserId(String userId);
Optional<User> findByEmail(String email);
// 소셜 로그인용
Optional<User> findByProviderAndProviderId(String provider, String providerId);
boolean existsByUserId(String userId);
boolean existsByEmail(String email);
}

GET http://localhost:8080/api/auth/google/login-url
응답:
{
"loginUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=http://localhost:3000/auth/google/callback&response_type=code&scope=openid email profile"
}
GET http://localhost:8080/api/auth/google/callback?code=4/0AeanBKo...
응답:
{
"success": true,
"message": "구글 로그인 성공",
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
"user": {
"id": 15,
"userId": "google_123456789",
"username": "김민수",
"email": "minsu@gmail.com",
"role": 1
}
}
-- 구글 로그인 사용자 조회
SELECT * FROM users
WHERE provider = 'GOOGLE'
ORDER BY created_at DESC;
결과:
+----+------------------+----------+------------------+----------+------------+------+--------+
| id | userId | username | email | provider | providerId | role | active |
+----+------------------+----------+------------------+----------+------------+------+--------+
| 15 | google_123456789 | 김민수 | minsu@gmail.com | GOOGLE | 123456789 | 1 | 1 |
+----+------------------+----------+------------------+----------+------------+------+--------+
[프론트엔드] [백엔드] [구글]
│ │ │
│──① URL 요청────▶│ │
│◀─② URL 반환─────│ │
│ │ │
│──③ 로그인────────────────────────▶│
│◀─④ 인증 코드──────────────────────│
│ │ │
│──⑤ code 전송────▶│ │
│ │──⑥ 토큰 요청──▶│
│ │◀─⑦ 토큰────────│
│ │──⑧ 정보 요청──▶│
│ │◀─⑨ 정보────────│
│ │ ⑩ DB + JWT │
│◀─⑪ JWT 반환─────│ │