OAuth2가 무엇인가?
OAuth2는 로그인을 대신해주는 표준 방식이다. 사용자가 비밀번호를 입력하지 않고, 구글/카카오 계정으로 로그인하는 것을 가능하게 한다.
기본 흐름(단순화)를 살펴보면
- 사용자가 구글 로그인 버튼 클릭
- Spring Security → 구글 서버로 리다이렉트
- 구글 로그인 → 우리 서버로 code 반환
- Spring Security가 code 로 access token 요청
- access token으로 사용자 정보 요청
- 사용자 정보 기반으로 인증 처리 완료
Spring Security OAuth2 구조를 이해해보자
Spring Security는 OAuth2 로그인을 처리하기 위해 다음 컴포넌트를 사용한다.
컴포넌트 | 역할 |
OAuth2LoginAuthenticationFilter | 로그인 요청 가로채기 |
DefaultOauth2UserService | 사용자 정보 조회 |
OAuth2User | 조회된 사용자 정보 모델 |
AuthenticationSuccessHandler | 로그인 성공 후 처리 |
ClientReistration | 클라이언트 정보(google, kakao 등) |
카카오 / 구글 연동을 위한 기본 설정 예시
// application.yml 예시
spring:
security:
oauth2:
client:
registration:
google:
client-id: <GOOGLE_CLIENT_ID>
client-secret: <GOOGLE_CLIENT_SECRET>
scope: profile, email
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: Google
kakao:
client-id: <KAKAO_REST_API_KEY>
client-secret: <KAKAO_CLIENT_SECRET>
scope: profile_nickname, account_email
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: Kakao
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
핵심 커스터마이징 포인트
1. 사용자 정보 가공 - CustomOAuth2UserService
구글 / 카카오에서 가져오는 사용자 정보는 정형화돼 있지 않다. 서비스마다 반환 형식도 다르고, 내가 원하는 도메인 정보와 맞지 않을 수도 있다.
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId(); // google, kakao
String userNameAttr = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
Map<String, Object> attributes = oAuth2User.getAttributes();
OAuthAttributes mappedAttributes = OAuthAttributes.of(registrationId, userNameAttr, attributes);
// DB에 사용자 저장
Member member = saveOrUpdate(mappedAttributes);
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
mappedAttributes.getAttributes(),
mappedAttributes.getNameAttributeKey());
}
}
2. 최초 로그인 시 회원가입 처리
private Member saveOrUpdate(OAuthAttributes attributes) {
Optional<Member> user = memberRepository.findByEmail(attributes.getEmail());
if (user.isEmpty()) {
return memberRepository.save(attributes.toEntity());
}
return user.get(); // 기존 사용자
}
3. 사용자 도메인 모델 매핑
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
public static OAuthAttributes of(String provider, String userNameAttributeName, Map<String, Object> attributes) {
if ("kakao".equals(provider)) {
return ofKakao(userNameAttributeName, attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
return new OAuthAttributes(attributes, userNameAttributeName,
(String) attributes.get("name"),
(String) attributes.get("email"));
}
private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
return new OAuthAttributes(attributes, userNameAttributeName,
(String) profile.get("nickname"),
(String) kakaoAccount.get("email"));
}
public Member toEntity() {
return new Member(name, email, Role.USER);
}
}
4. JWT 발급 연동
OAuth2 로그인 성공 후 JWT를 발급하고 응답에 실어주는 구조도 자주 사용한다.
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
private final JwtUtil jwtUtil;
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
String email = (String) oAuth2User.getAttributes().get("email");
String token = jwtUtil.createToken(email);
response.setHeader("Authorization", "Bearer " + token);
}
}
전체 흐름을 요약해보면
- 사용자가 /oauth2/authorication/kakao 클릭
- 카카오 로그인
- code 반환
- Spring이 token 요청
- 사용자 정보 조회
- CustomOAuth2UserService 실행
- DB 회원가입 처리
- SecurityContext 인증 저장
- SuccessHandler에서 JWT 발급
- 클라이언트로 응답
OAuth2 로그인은 단순해 보이지만 실제 서비를 만들 때는 사용자 정보 가공, JWT 발급, 회원가입 처리 등 커스터마이징이 꼭 필요한 포인트들이 많다.
'Back-End > Spring Boot + Kotlin' 카테고리의 다른 글
Spring Security는 뭘까? (2) | 2025.06.16 |
---|---|
Spring Bean? (5) | 2025.06.12 |
DB Connection Pool 고갈이 되면 무슨 일이 벌어질까? (0) | 2025.05.05 |
Kotlin val 은 왜 값이 변하는가?? (0) | 2025.04.28 |
Spring Boot 의존성 주입 (Dependency Injection) (2) | 2025.04.16 |