이 포스트에서는 스프링 부트로 소셜 로그인을 구현해보겠습니다. 스프링 시큐리티와 스프링 OAuth2 클라이언트 라이브러리를 사용하여 구글, 네이버, 카카오 서비스와 연동하여 로그인, 회원 탈퇴 기능이 들어간 간단한 애플리케이션을 구현해보겠습니다.
OAuth2 연동을 하기 위해서 먼저 OAuth2 제공자인 구글, 네이버, 카카오에서 OAuth2 애플리케이션을 생성해야합니다. 각 서비스 별로 애플리케이션을 생성하는 방법은 비슷합니다. 애플리케이션을 생성하면 Client ID
와 Client Secret
이 생성됩니다. 이 두개의 값은 앞으로 만들 스프링 애플리케이션에서 사용합니다.
https://console.cloud.google.com 에서 상단의 프로젝트 선택을 누르고 프로젝트를 선택합니다. 처음에는 프로젝트가 없으므로 새 프로젝트를 선택하여 생성합니다.
프로젝트 이름을 입력하여 생성합니다.
상단에서 생성한 프로젝트를 선택하고 메뉴 -> API 및 서비스 -> OAuth 동의 화면을 선택합니다. User Type은 외부를 선택하고 동의 화면을 만듭니다.
앱 정보에 앱 이름, 사용자 지원 이메일, 개발자 연락처 정보를 입력합니다.
범위에서 email, profile, openid 를 선택합니다.
테스트 사용자를 입력합니다. (이메일 주소)
메뉴 -> API 및 서비스 -> 사용자 인증 정보로 이동합니다. 사용자 인증 정보 만들기 -> OAuth 클라이언트 ID를 선택합니다.
애플리케이션 유형은 웹 애플리케이션, 애플리케이션 이름을 입력합니다. 승인된 리디렉션 URI에 http://localhost:8080/login/oauth2/code/google
을 입력하여 OAuth 클라이언트 ID를 생성합니다.
클라이언트 ID가 Client ID
값 입니다. 클라이언트 보안 비밀번호는 Client Secret
값 입니다. 나중에 쓸 값이므로 저장해둡니다.
https://developers.naver.com 에서 Application -> 애플리케이션 등록을 선택합니다.
아래와 같이 애플리케이션 이름을 입력합니다. 사용 API는 네이버 로그인을 선택합니다. 필수 정보로 이름, 이메일, 별명, 프로필 사진을 선택합니다.
환경은 PC 웹 환경을 선택하고 서비스 URL은 테스트 용도이므로 http://localhost:8080
네이버 로그인 Callback URL은 http://localhost:8080/login/oauth2/code/naver
을 입력합니다.
애플리케이션을 생성하면 Client ID
와 Client Secret
정보를 발급받습니다. 이 정보는 유출되지 않도록 합니다.
https://developers.kakao.com 에서 내 애플리케이션 -> 애플리케이션 추가하기를 선택합니다.
아래와 같이 애플리케이션 이름과 사업자명을 입력하여 애플리케이션을 생성합니다.
메뉴에서 앱 설정 -> 앱 키를 들어가면 나오는 REST API 키가 Client ID
값 입니다.
메뉴에서 제품 설정 -> 카카오 로그인을 선택합니다. 활성화 설정을 ON 으로 설정합니다. Redirect URI에 http://localhost:8080/login/oauth2/code/kakao
를 입력합니다.
메뉴에서 제품 설정 -> 카카오 로그인 -> 동의 항목을 선택합니다. 아래와 같이 닉네임, 프로필 사진을 필수 동의로 설정합니다. 카카오 계정(이메일)은 애플리케이션 검수를 받아야 필수 동의로 설정할 수 있습니다. 테스트 용도 이므로 일단 선택 동의로 설정합니다.
메뉴에서 제품 설정 -> 카카오 로그인 -> 보안을 선택합니다. 아래와 같이 코드 생성 버튼을 선택하여 Client Secret
을 생성하고 활성화 상태를 사용함으로 설정합니다.
https://start.spring.io 에서 아래와 같이 의존성을 추가하여 프로젝트를 생성합니다.
인텔리제이에서 프로젝트를 열어 메인 클래스를 실행합니다. http://localhost:8080 에 접속되는지 확인합니다.
프로젝트에 기본으로 생성된 src/main/resources/application.properties
파일을 삭제하고 src/main/resources/application.yml
파일을 생성합니다. 아래 내용을 추가해줍니다. 앞에서 각 서비스별로 생성했던 Client ID와 Client Secret을 적어줍니다.
spring:
security:
oauth2:
client:
registration:
google:
client-id: # 발급 받은 Client ID
client-secret: # 발급 받은 Client Secret
scope: # 기본 값 openid,profile,email => profile, email 로 설정 변경
- profile
- email
naver:
client-id: # 발급 받은 Client ID
client-secret: # 발급 받은 Client Secret
client-authentication-method: client_secret_post
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
# scope:
# - name
# - email
# - profile_image
client-name: Naver
kakao:
client-id: # 발급 받은 Client ID
client-secret: # 발급 받은 Client Secret
client-authentication-method: client_secret_post
authorization-grant-type: authorization_code
scope: # https://developers.kakao.com/docs/latest/ko/kakaologin/common#user-info
- profile_nickname
- profile_image
- account_email
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
client-name: Kakao
provider:
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-info-authentication-method: header
user-name-attribute: response # Naver 응답 값 resultCode, message, response 중 response 지정
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-info-authentication-method: header
user-name-attribute: id # Kakao 응답 값 id, connected_at, properties, kakao_account 중 id 지정
Google은 scope 필드의 기본 값이 openid, profile, email 입니다. 이를 profile, email 로 설정을 변경합니다. openid가 scope에 있으면 Open Id Provider로 인식하기 때문입니다.
client-authentication-method: 인가 코드로 액세스 토큰을 요청할 때 방법을 정의합니다.
authorization-uri: 소셜 로그인 버튼을 눌렀을 때 이동 할 URL 입니다. 해당 URL로 이동하면 소셜 로그인과 사용자 동의를 진행하고 인가 코드를 발급하여 사용자 서비스 서버로 리다이렉트 합니다.
token-uri: 사용자 서비스 서버가 Redirect URI로 전달받은 인가 코드로 액세스 토큰을 요청하는 URI 입니다.
user-info-uri: 서비스 서버가 발급받은 액세스 토큰으로 사용자 정보를 가져오는 URI 입니다.
user-info-authentication-method: 사용자 정보를 가져올 때 토큰을 보내는 방법 입니다. header, form, query 중 header 가 기본 값 입니다. 기본 값은 헤더에 액세스 토큰을 담아서 보내는 것 입니다.
Google, GitHub, Facebook, Okta는 CommonOAuth2Provider
enum 에 미리 정의되어 있습니다. Google에 scope 값이 openid, profile, email이 설정되어 있는 것을 볼 수 있습니다.
아래는 client-authentication-method
값이 정의되어 있는 코드입니다.
아래 코드를 보면 client-authentication-method
값이 client_secret_post
이면 파라미터에 clientId
, clientSecret
을 담는 것을 볼 수 있습니다. 네이버, 카카오는 인가 코드로 토큰 요청시 clientId
, clientSecret
값을 담아서 보내기 때문에 client_secret_post
을 사용합니다.
아래는 user-info-authentication-method
의 값이 정의되어 있는 코드입니다.
RestTemplate은 OAuth2 연결 끊기 기능 구현시 HTTP API 요청을 위해 사용합니다. RestTemplate 객체를 스프링 빈으로 등록하기 위한 설정 클래스입니다.
OAuth2 인증 관련 실패를 위한 사용자 정의 예외 클래스입니다.
OAuth2 인증 실패시 호출되는 핸들러입니다. 인증 실패시 처음 프론트엔드에서 백엔드로 로그인 요청시 redirect_uri
쿼리 파라미터에 담긴 주소로 리다이렉트 합니다. 이 때 error
라는 쿼리 파라미터에 오류 메세지를 담아서 리다이렉트합니다.
OAuth2 인증 성공시 호출되는 핸들러입니다. 처음 프론트엔드에서 백엔드로 로그인 요청시 mode
쿼리 파라미터에 담긴 값에 따라 분기하여 처리합니다. mode
값이 login
이면 사용자 정보를 DB에 저장하고, 서비스 자체 액세스 토큰, 리프레시 토큰을 생성하고, 리프레시 토큰을 DB에 저장합니다. mode
값이 unlink
이면 각 OAuth2 서비스에 맞는 연결 끊기 API를 호출하고, 사용자 정보를 DB에서 삭제하고, 리프레시 토큰을 DB에서 삭제합니다.
DefaultOAuth2UserService
클래스를 상속 받아 구현한 사용자 정의 클래스입니다. loadUser
메서드는 스프링 시큐리티 OAuth2LoginAuthenticationFilter
에서 시작된 OAuth2 인증 과정 중에 호출됩니다. 호출되는 시점은 액세스 토큰을 OAuth2 제공자로부터 받았을 때 입니다.
먼저 super.loadUser
를 통해 상위 클래스에 정의된 액세스 토큰으로 사용자 정보를 가져오는 로직을 실행해야합니다.
그 이후 processOAuth2User
메서드를 통해 각 OAuth2 제공자 별 제공되는 사용자 정보를 동일한 인터페이스로 변환하여 리턴합니다.
위에서 보았던 processOAuth2User
에서 리턴하는 인증 객체는 OAuth2User 인터페이스 입니다. 이 인터페이스의 사용자 정의 구현체 클래스입니다.
OAuth2 제공자 별로 리턴하는 사용자 정보 데이터의 구조와 필드 이름 등이 다릅니다. 예를 들면 아래는 네이버에서 리턴하는 사용자 정보입니다. 서비스 별로 다른 구조를 통합하기 위한 인터페이스를 정의합니다.
구글 사용자 정보를 가져와 저장하는 구현체입니다. 아래와 같은 JSON 데이터가 리턴됩니다.
{
"sub": "....",
"name": "....",
"given_name": "....",
"family_name": "....",
"picture": "https....",
"email": "....",
"email_verified": true,
"locale": "ko"
}
네이버 사용자 정보를 가져와 저장하는 구현체입니다.
카카오 사용자 정보를 가져와 저장하는 구현체입니다.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
OAuth2 제공자 별로 OAuth2 애플리케이션과 연동 해제 하는 방법이 다릅니다. 서비스 별로 다른 연동 해제 방법을 통합 하기 위한 인터페이스를 정의합니다.
구글 OAuth2 연동 해제를 위한 구현체입니다.
네이버 OAuth2 연동 해제를 위한 구현체입니다.
카카오 OAuth2 연동 해제를 위한 구현체입니다.
OAuth2 서비스를 구분하기 위한 enum 입니다.
OAuth2 인증시 액세스 토큰으로 사용자 정보를 가져왔을 때, OAuth2 제공자 별로 분기하여 OAuth2UserInfo 인터페이스 구현체를 호출하여 OAuth2UserInfo 객체를 생성해주는 팩토리 클래스입니다.
OAuth2 연동 해제시 OAuth2 제공자 별로 분기하여 OAuth2UserUnlink 인터페이스 구현체를 호출하여 연동 해제해주는 클래스입니다.
쿠키에 객체를 직렬화하여 저장하고 역직렬화하여 불러오는 기능을 구현한 클래스입니다. OAuth2 인증 과정에서 프론트엔드에서 백엔드로 최초 요청시 쿼리 파라미터를 인증이 끝날때까지 유지하기 위해 데이터를 쿠키에 직렬화하여 저장합니다. 또한 OAuth2 인증 과정중에 사용하는 state
파라미터 등을 저장합니다.
OAuth2 인증 과정중에 state
, redirect_uri
등의 파라미터를 어딘가에 저장해야하는데 이를 쿠키에 저장하는 방식을 구현한 사용자 정의 클래스입니다. 스프링 빈으로 등록하고 SecurityConfig 에서 authorizationRequestRepository
로 설정합니다.
스프링 시큐리티 OAuth2 관련 필터인 OAuth2AuthorizationRequestRedirectFilter
와 OAuth2LoginAuthenticationFilter
에서 인증 과정중에 호출됩니다.
최초에 프론트엔드에서 로그인 요청시 리다이렉트 할 OAuth2 제공자 별 URL 정보를 쿠키에 저장하여 리다이렉트 합니다. 그 이후 사용자가 로그인 성공시 백엔드로 리다이렉트 될 때 인증 과정 및 사용자 정보 불러오는 과정을 마친 후 쿠키에 저장된 정보를 삭제합니다.
스프링 애플리케이션을 시작하는 메인 클래스입니다.
https://github.com/nefertirii/oauth2-demo/tree/f66c594581a068f8a7fd0c14b7513db920939e6a
너무 감탄해서 말이 안 나옵니다.. 좋은 하루 되십쇼 !!