이번 글에서는 OAuth2를 활용한 소셜 로그인에 대해서 정리해보려고 합니다. 해당 방법은 기초에 해당되는 방법이라고 생각하기 때문에 소셜 로그인을 처음 적용해보는 분들에게 도움이 될 것 같습니다.
제 3자 애플리케이션이 사용자 대신 안전하게 리소스에 접근할 수 있도록 허용하는 인증 프로토콜입니다. 이를 통해 사용자는 비밀번호를 공유하지 않고도 애플리케이션에 접근 권한을 부여할 수 있습니다.
🔷 주요 장점
1. 사용자는 비밀번호를 직접 입력하지 않아도 됩니다.
2. 앱은 사용자의 소셜 계정 정보를 안전하게 받아올 수 있습니다.
3. 사용자는 언제든 앱의 접근 권한을 취소할 수 있습니다.
👉 OAuth2 인증 과정의 주요 단계
build.gradle
파일에 해당 코드 추가합니다. dependencies {
// ... 기존 의존성 ...
// Spring Security OAuth2 클라이언트 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
구글, 카카오, 네이버 세 가지 모두 진행할 예정입니다. 각각의 해당 사이트에 들어가 계정 생성 및 애플리케이션 등록을 완료해야합니다. 해당 내용은 구글링하면 쉽게 찾을 수 있으니 생략하도록 하겠습니다.
🔷 참고로, 계정 생성 및 애플리케이션 등록 과정을 거치면서 전달받을 정보의 범위를 선택할 수 있는 창이 나오는데 이때 선택하는 정보에 따라 가지고 올 수 있는 정보가 한정됩니다. 또한, 구글, 카카오, 네이버에 따라 가지고 올 수 있는 개인정보가 다릅니다.
개인적으로, 이 부분에서 실수가 많이 나올 수 있다고 생각합니다! 사이트에 따라 특성이 조금씩 다를 수도 있다는 것을 인지한 상태로 문제를 해결해보는 것을 추천합니다.
application.yml
파일에 해당 내용을 추가합니다. spring:
# --- 이전 설정 ---
security:
oauth2:
client:
registration:
kakao:
client-name: Kakao
client-authentication-method: client_secret_post
client-id: { 본인 client-id }
client-secret: { 본인 client-secret}
redirect-uri: { 본인 도메인 }/login/oauth2/code/kakao
authorization-grant-type: authorization_code
scope: profile_nickname
google:
client-name: Google
client-id: { 본인 client-id }
client-secret: { 본인 client-secret}
redirect-uri: { 본인 도메인 }/login/oauth2/code/google
authorization-grant-type: authorization_code
scope: profile,email
naver:
client-name: Naver
client-id: { 본인 client-id }
client-secret: { 본인 client-secret}
authorization-grant-type: authorization_code
redirect-uri: { 본인 도메인 }/login/oauth2/code/naver
scope: name
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
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
scope
이란?앱이 요청하는 권한으로, 즉 해당 소셜 사이트에서 가져올 수 있는 개인 정보를 의미합니다. 이는 naver 소셜 로그인을 통해서는 이름을, google에서는 profile과 email을 가져올 수 있다는 것을 의미합니다.
인증 후 소셜 사이트에서 사용자를 리다이렉트할 URI입니다. 사이트에서 설정한 URI와 일치해야 합니다.
이 클라이언트 등록의 사람이 읽을 수 있는 이름입니다. 나중에 CustomOAuth2UserService
와 연동되니 형식을 통일하여 적는 걸 추천합니다. 처음에 그걸 모르고 소문자, 대문자 섞어썼다가 오류가 떴습니다..ㅎㅎ
client-id
, client-secret
, redirect-uri
각자 추가해넣기client-id
, client-secret
, redirect-uri
는 각자 등록한 계정마다 다르기 때문에 해당 코드를 지우고 각자의 것을 작성하면 됩니다. 이는 노출이 되지 않는 것이 좋기 때문에 application-secret.yml로 따로 관리를 하거나 intelliJ 설정을 통해 등록해놓는 것이 좋습니다.
사용할 OAuth 2.0 흐름을 지정합니다. authorization_code
는 웹 애플리케이션의 표준 흐름입니다.
Spring이 카카오에 클라이언트 자격 증명을 어떻게 보낼지 지정합니다. client_secret_post
는 POST 본문으로 보낸다는 의미입니다.
provider
섹션authorization-uri
: 사용자가 카카오로 로그인하기 위해 리다이렉트되는 URL입니다.token-uri
: 애플리케이션이 인증 코드를 액세스 토큰으로 교환하는 URL입니다.user-info-uri
: 애플리케이션이 액세스 토큰을 사용하여 사용자 정보를 가져올 수 있는 URL입니다.user-name-attribute
: 사용자 정보 응답에서 주요 이름으로 사용할 속성을 지정합니다.provider
를 개별적으로 적지 않아도 구현 가능합니다. application 파일은 yml 형식과 properties 형식이 있는데 위와 같은 yml 파일은 들여쓰기가 매우 중요하기 때문에 유의해주세요!
소셜 로그인 후 받은 사용자 정보를 우리 DB에 저장하는 CustomOAuth2UserService
를 만들어볼 차례입니다.
CustomOAuth2Service
파일 위치: src/main/java/project/config/security
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String clientName = userRequest.getClientRegistration().getClientName();
Map<String, Object> attributes = oAuth2User.getAttributes();
String email = null;
String name = null;
// 로그 추가: clientName과 attributes 확인
System.out.println("Client Name: " + clientName);
System.out.println("Attributes: " + attributes);
if ("Kakao".equals(clientName)) {
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
name = (String) properties.get("nickname");
email = name + "@kakao.com"; // 임시 이메일 생성
} else if ("Google".equals(clientName)) {
name = (String) attributes.get("name");
email = (String) attributes.get("email");
} else if ("Naver".equals(clientName)) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
name = (String) response.get("name");
email = name + "@naver.com"; // 임시 이메일 생성
}
// 로그 추가: name과 email 값 확인
System.out.println("Name: " + name);
System.out.println("Email: " + email);
// 사용자 정보 저장 또는 업데이트
Member member = saveOrUpdateUser(email, name);
// 이메일을 Principal로 사용하기 위해 attributes 수정
Map<String, Object> modifiedAttributes = new HashMap<>(attributes);
modifiedAttributes.put("email", email);
return new DefaultOAuth2User(
oAuth2User.getAuthorities(),
modifiedAttributes,
"email" // email Principal로 설정
);
}
private Member saveOrUpdateUser(String email, String name) {
if (name == null || name.isEmpty()) {
name = "default_name";
}
Member member = memberRepository.findByEmail(email)
.orElse(Member.builder()
.email(email)
.name(name)
.password(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID()))
.gender(Gender.NONE)
.address("소셜로그인")
.specAddress("소셜로그인")
.role(Role.USER)
.build());
return memberRepository.save(member);
}
}
해당 클래스는 소셜 로그인 후 받은 사용자 정보를 처리하고 데이터베이스에 저장하는 역할을 합니다. 주요 기능은 다음과 같습니다.
super.loadUser(userRequest)
를 통해 소셜에서 제공하는 사용자 정보를 OAuth2User 객체로 받아옵니다. saveOrUpdateUser
메소드를 통해 사용자 정보를 DB에 저장하거나 업데이트합니다. DefaultOAuth2User
객체를 생성하여 반환합니다.
System.out.println
으로 로그 출력하는 부분은 확인을 위한 코드로 생략하셔도 무방합니다.
if ("Kakao".equals(clientName)) {
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
name = (String) properties.get("nickname");
email = name + "@kakao.com"; // 임시 이메일 생성
} else if ("Google".equals(clientName)) {
name = (String) attributes.get("name");
email = (String) attributes.get("email");
} else if ("Naver".equals(clientName)) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
name = (String) response.get("name");
email = name + "@naver.com";
}
이 부분이 아까 application.yml
에서 설명한 client-name
이 등장합니다. (Kakao, Google, Naver)
💡주의사항
- 실제 서비스에서는 닉네임 대신 고유한 식별자(예: 이메일, 카카오 고유 ID)를 사용하는 것이 더 안정적입니다.
- 소셜 로그인 후 추가 정보 입력 페이지를 통해 필요한 사용자 정보를 수집하는 것이 좋습니다.
SecurityConfig
에 OAuth2 관련 설정을 추가합니다. @EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ... 기존 설정 ...
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
);
return http.build();
}
// 기존 빈 설정...
}
src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<!-- 기존 코드 -->
<!-- 소셜 로그인 -->
<a th:href="@{/oauth2/authorization/kakao}">카카오로 로그인</a><p/>
<a th:href="@{/oauth2/authorization/google}">구글로 로그인</a><p/>
<a th:href="@{/oauth2/authorization/naver}">네이버로 로그인</a><p/>
</body>
</html>
소셜로그인이 잘 작동되는지 확인할 수 있게끔, 로그인이 완료된 후 보여질 home 화면도 만드는 것을 추천드립니다.
1️⃣ 카카오
2️⃣ 구글
3️⃣ 네이버
전체 Github 코드 https://github.com/Kkimdoyeon/umc_study2/tree/feature/login
Copyright © 2024 김준석(벡스) All rights reserved.