본 캠프_70일차

졸용·2025년 6월 2일

TIL

목록 보기
71/144

✅ Google OAuth2 연동

구현하고자 하는 프로젝트의 구조:

  1. Google OAuth2 로그인을 통해 인증

  2. 로그인 성공 시, Google Access Token과 ID Token을 받아 옴.

    서버에서:

  3. Google Token을 검증하여 사용자가 진짜인지 확인

  4. 필요하면 회원가입 처리 (구글 계정 기반)

  5. 이후 서버에서 자체 JWT 발급 (Access Token + Refresh Token)

  6. 클라이언트는 이후 자체 JWT를 사용해 API 요청.

  7. 로그아웃 시 토큰 Redis 블랙리스트에 등록하여 처리.
[Client][Google OAuth2 인증 → Access Token 획득][Spring 서버]
   1. Google Token 검증 (tokeninfo or public key)
   2. 사용자 정보로 DB 조회 or 가입 처리
   3. 서버 전용 JWT (Access + Refresh) 발급
   ↓
[Client] ← 자체 JWT 사용 시작

흐름의 순서대로 보면서 논리적 연결과 역할 분리를 확인해보았다.


✅ 1. CustomOAuth2UserService

  • Google 로그인 시 사용자 정보 가져오기 및 신규 유저 처리 관련

✅ 2. OAuth2SuccessHandler

  • 로그인 성공 이후, JWT 발급 및 리디렉션 처리

✅ 3. JwtProvider

  • JWT 생성, 검증, 파싱 등 핵심 로직

✅ 4. JwtFilter

  • 요청 시 JWT 검사 및 SecurityContext 등록

✅ 5. SecurityConfig

  • Spring Security 전반 설정 (필터, 인가 규칙 등)

✅ 6. AuthService

  • JWT 재발급, 로그아웃 등 비즈니스 로직

✅ 7. AuthController

  • 인증 관련 엔드포인트

✅ 8. TokenResponseDto, TokenReissueRequestDto, OAuth2LoginRequestDto

  • 요청/응답 DTO (최종적으로 점검)


1️⃣ Google OAuth2 클라이언트 기준으로 구현할 때 필요한 application.properties

# OAuth2 Client Registration - Google
spring.security.oauth2.client.registration.google.client-id=YOUR_GOOGLE_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=YOUR_GOOGLE_CLIENT_SECRET
spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.google.scope=profile,email

# OAuth2 Provider - Google
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth
spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
spring.security.oauth2.client.provider.google.user-name-attribute=sub

2️⃣ SecurityConfig - OAuth2 로그인 설정 추가

.oauth2Login(oauth2 -> oauth2
    .userInfoEndpoint(userInfo -> userInfo
        .userService(customOAuth2UserService) // OAuth2 사용자 정보 처리
    )
    .successHandler(oAuth2SuccessHandler)     // 로그인 성공 후 JWT 발급
)

3️⃣ CustomOAuth2UserService 구현 - 구글 사용자 정보 가져오기

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        String email = oAuth2User.getAttribute("email");
        String name = oAuth2User.getAttribute("name");

        // 사용자 등록 또는 조회
        User user = userRepository.findByEmail(email)
            .orElseGet(() -> userRepository.save(User.builder()
                .email(email)
                .name(name)
                .provider("google")
                .build()));

        // 실제로는 CustomOAuth2User 객체를 만들어 반환할 수도 있음
        return oAuth2User;
    }
}

4️⃣ OAuth2SuccessHandler 구현 - 자체 JWT 발급

OAuth2SuccessHandler 클래스는 Spring Security의 OAuth2 인증 흐름에서 OAuth2 로그인에 성공한 이후 실행되는 후처리 로직을 담당한다.

OAuth2 로그인 후, 서버에서 자체 JWT를 발급하고 응답을 구성하는 처리 담당자

✔️ 왜 별도 클래스가 필요할까?

  • Spring Security는 기본적으로 OAuth2 로그인 성공 후 그냥 /loginSuccess 같은 페이지로 이동함.

  • 하지만 우리는 자체 토큰 기반 인증 시스템을 사용하기 때문에,

    OAuth2 로그인 이후 → JWT 발급 → 전달
    라는 커스텀 로직이 필요하다.

그래서 AuthenticationSuccessHandler를 구현해서 우리가 원하는 방식으로 동작을 정의하는 것이다.


여기까지 했을 때 프로젝트 흐름의 구조:


5️⃣ AuthService 구현 - 로그아웃 / 토큰 재발급 등

토큰 재발급 등 HTTP 요청 처리의 중간 계층으로 필요하다.

비즈니스 로직(예: 로그인 흐름, 토큰 재발급, 로그아웃 등)을 종합적으로 처리하는 서비스

@Service
@RequiredArgsConstructor
public class AuthService {

	private final UserRepository userRepository;
	private final JwtProvider jwtProvider;
	private final StringRedisTemplate redisTemplate;

	// Google OAuth2 로그인 성공 시 토큰 발급
	public TokenResponseDto issueToken(User user) {

		String accessToken = jwtProvider.createAccessToken(user.getId(), user.getEmail(), user.getNickname());
		String refreshToken = jwtProvider.createRefreshToken(user.getId());

		// Redis 에 Refresh Token 저장
		jwtProvider.storeRefreshToken(user.getEmail(), refreshToken);

		return new TokenResponseDto(accessToken, refreshToken);
	}

	// 로그아웃 - Access Token 블랙리스트 처리 + Refresh Token 삭제
	public void logout(String accessToken, String email) {

		String pureToken = jwtProvider.subStringToken(accessToken);
		long expiration = jwtProvider.getClaims(accessToken).getExpiration().getTime() - System.currentTimeMillis();

		// Access Token 블랙리스트 등록
		redisTemplate
			.opsForValue()
			.set("BLACKLIST_" + pureToken, "logout", expiration, TimeUnit.MILLISECONDS);

		// Refresh Token 삭제
		jwtProvider.deleteRefreshToken(email);
	}

	// Refresh Token 을 이용한 Access Token 재발급
	public TokenResponseDto reissue(String email, String refreshToken) {

		if (!jwtProvider.isValidRefreshToken(email, refreshToken)) {
			throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Refresh Token이 유효하지 않습니다.");
		}

		User user = userRepository.findByEmail(email)
			.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "사용자 정보를 찾을 수 없습니다."));

		String newAccessToken = jwtProvider.createAccessToken(user.getId(), user.getEmail(), user.getNickname());
		String newRefreshToken = jwtProvider.createRefreshToken(user.getId());

		jwtProvider.deleteRefreshToken(email);
		jwtProvider.storeRefreshToken(email, newRefreshToken);

		return new TokenResponseDto(newAccessToken, newRefreshToken);
	}
}

6️⃣ AuthController 구현 - 엔드포인트 담당

로그인, 로그아웃, 토큰 재발급 등 인증 관련 API 엔드포인트를 담당한다.

  • 소셜 로그인 완료 후 토큰 발급

  • AccessToken 블랙리스트 등록, RefreshToken 제거 - 로그아웃 처리

  • RefreshToken으로 AccessToken 재발급 - 토큰 만료 시 자동 연장

profile
꾸준한 공부만이 답이다

0개의 댓글