구현하고자 하는 프로젝트의 구조:
- Google OAuth2 로그인을 통해 인증
- 로그인 성공 시, Google Access Token과 ID Token을 받아 옴.
서버에서:
- Google Token을 검증하여 사용자가 진짜인지 확인
- 필요하면 회원가입 처리 (구글 계정 기반)
- 이후 서버에서 자체 JWT 발급 (Access Token + Refresh Token)
- 클라이언트는 이후 자체 JWT를 사용해 API 요청.
- 로그아웃 시 토큰 Redis 블랙리스트에 등록하여 처리.
![]() |
|---|
[Client]
↓
[Google OAuth2 인증 → Access Token 획득]
↓
[Spring 서버]
1. Google Token 검증 (tokeninfo or public key)
2. 사용자 정보로 DB 조회 or 가입 처리
3. 서버 전용 JWT (Access + Refresh) 발급
↓
[Client] ← 자체 JWT 사용 시작
흐름의 순서대로 보면서 논리적 연결과 역할 분리를 확인해보았다.
CustomOAuth2UserServiceOAuth2SuccessHandlerJwtProviderJwtFilterSecurityConfigAuthServiceAuthControllerTokenResponseDto, TokenReissueRequestDto, OAuth2LoginRequestDto# 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
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService) // OAuth2 사용자 정보 처리
)
.successHandler(oAuth2SuccessHandler) // 로그인 성공 후 JWT 발급
)
@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;
}
}
OAuth2SuccessHandler 클래스는 Spring Security의 OAuth2 인증 흐름에서 OAuth2 로그인에 성공한 이후 실행되는 후처리 로직을 담당한다.
OAuth2 로그인 후, 서버에서 자체 JWT를 발급하고 응답을 구성하는 처리 담당자
Spring Security는 기본적으로 OAuth2 로그인 성공 후 그냥 /loginSuccess 같은 페이지로 이동함.
하지만 우리는 자체 토큰 기반 인증 시스템을 사용하기 때문에,
OAuth2 로그인 이후 → JWT 발급 → 전달
라는 커스텀 로직이 필요하다.
그래서 AuthenticationSuccessHandler를 구현해서 우리가 원하는 방식으로 동작을 정의하는 것이다.
여기까지 했을 때 프로젝트 흐름의 구조:
![]() |
|---|
토큰 재발급 등 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);
}
}
로그인, 로그아웃, 토큰 재발급 등 인증 관련 API 엔드포인트를 담당한다.
소셜 로그인 완료 후 토큰 발급
AccessToken 블랙리스트 등록, RefreshToken 제거 - 로그아웃 처리
RefreshToken으로 AccessToken 재발급 - 토큰 만료 시 자동 연장