OAuth 2.0 로그인 : 구글/네이버로 로그인하기

jyleever·2022년 4월 11일
0

구글로 로그인하기
네이버로 로그인하기

스프링부트 + 스프링 시큐리티 + OAuth 2.0

1. OAuth 서비스 등록

각 서비스 등록하는 방법은 예전에 공부하면서 작성한 게시물에 잘 정리되어있으니 생략한다. 단, 구글에서 범위 추가하는 것은 아래 따로 첨부


http://localhost:8080/login/oauth2/code/google

네이버는 미리 만들어둔 API를 사용했다.

2. build.gradle

  • oauth2-client 의존성을 추가한다
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

SecurityConfig

			...
			.and()
			.oauth2Login()
			.userInfoEndpoint() /* OAuth 2 로그인 성공 후 사용자 정보를 가져올 떄의 설정 담당 */
			.userService(customOAuth2UserService); /* 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스 구현체 등록, 사용자 정보를 가져온 상태에서 추가 진행 기능 명시 */

순서를 formLogin -> oauth2Login -> logout 즉,
formlogin()....and().oauth2Login()...and().logout 하면 에러가 난다. 그래서 oauth2Login()을 맨 마지막에 넣었더니 에러가 뜨지 않았다.

  • 왜 에러가 뜰까..?😥

User entity

	/* 소셜 로그인 시 이미 등록된 회원인 경우 수정 날짜 업데이트, 기존 데이터는 보존 */
	public User updateModifiedDate() {
		this.onPreUpdate(); // 수정 날짜 업데이트
		return this;
	}

기존에는 닉네임도 업데이트하도록 설계했지만 어차피 회원 설정에서 닉네임도 수정하기 때문에 수정날짜(modifiedDate)만 업데이트하도록 했다.

UserRepository

	/* OAuth 로그인 시 중복 체크 */
	Optional<User> findByEmail(String email);

CustomOAuth2UserService

implements OAuth2UserService<OAuth2UserRequest, OAuth2User>

package com.jy.config.oauth;

import java.util.Collections;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import com.jy.config.oauth.provider.GoogleUserInfo;
import com.jy.config.oauth.provider.NaverUserInfo;
import com.jy.config.oauth.provider.OAuth2UserInfo;
import com.jy.domain.user.User;
import com.jy.domain.user.UserRepository;
import com.jy.web.dto.UserDto;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/* Security UserDetailsService == OAuth OAuth2UserService */

@Slf4j
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User>{
	
	private final UserRepository userRepository;
	private final HttpSession session;
	
	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{
		
		/* ?? */
		OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
		OAuth2User oAuth2User = delegate.loadUser(userRequest);
		
		/* OAuth2 서비스 ID 구분 코드 - 구글, 네이버 */
		OAuth2UserInfo oAuth2UserInfo = null;
		
		if(userRequest.getClientRegistration().getRegistrationId().equals("google")) {
			log.info("구글 로그인 요청");
			oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
		}else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
			log.info("네이버 로그인 요청");
			oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
			/* JSON 형태이므로 Map을 통해 데이터를 가져옴 */
		}
		
		User user = saveOrUpdate(oAuth2UserInfo);
		
		/* 세션 사용자 정보를 저장하는 직렬화된 DTO 클래스 */
		session.setAttribute("user", new UserDto.ResponseUserDto(user));
		
		return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(user.getRole().getValue())) ,
									oAuth2UserInfo.getAttributes(), oAuth2UserInfo.getNameAttributeKey());
		
	}
	
	/* 소셜 로그인 시 기존 회원이 존재하는 경우 수정 날짜 정보만 업데이트. 만약 이름 등 프로필 정보가 변경된 경우도 업데이트 */
	private User saveOrUpdate(OAuth2UserInfo userInfo) {
		User user = userRepository.findByEmail(userInfo.getEmail())
					.map(entity -> entity.updateModifiedDate())
					.orElse(userInfo.toEntity());
		log.info("username:"+user.getUsername());
		log.info("nickname:"+user.getNickname());
		log.info("createdDate:"+user.getCreatedDate());
		log.info("role:"+user.getRole());
		
		return userRepository.save(user);
	}

}

GoogleUserInfo

package com.jy.config.oauth.provider;

import java.util.Map;

import com.jy.domain.Role;
import com.jy.domain.user.User;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GoogleUserInfo implements OAuth2UserInfo{
	private Map<String, Object> attributes;
	private String nameAttributeKey;
	private String username;
	private String nickname;
	private String email;
		
	public GoogleUserInfo(Map<String, Object> attributes) {
		this.attributes = attributes;
	}

	@Override
	public Map<String, Object> getAttributes() {
		return attributes;
	}

	@Override
	public String getNameAttributeKey() {
		nameAttributeKey = "sub";
		return nameAttributeKey;
	}

	@Override
	public String getUsername() {
		username = (String)attributes.get("email");
		return username;
	}

	@Override
	public String getNickname() {
		nickname = (String)attributes.get("name");
		return nickname;
	}

	@Override
	public String getEmail() {
		email = (String)attributes.get("email");
		return email;
	}

	@Override
	public User toEntity() {
		return User.builder()
					.username(getUsername())
					.email(getEmail())
					.nickname(getNickname())
					.role(Role.SOCIAL)
					.build();
	}
}

NaverUserInfo

package com.jy.config.oauth.provider;

import java.util.Map;

import com.jy.domain.Role;
import com.jy.domain.user.User;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NaverUserInfo implements OAuth2UserInfo{

	private Map<String, Object> response;
	/*
	 * { id = ...
	 * 	 email = ...
	 *   name = ...
	 * }
	 */
	
	/* 생성자 */
	public NaverUserInfo(Map<String, Object> attributes) {
		this.response = (Map<String, Object>)attributes.get("response");
	}
	
	@Override
	public Map<String, Object> getAttributes() {
		return response;
	}

	@Override
	public String getNameAttributeKey() {
		return "id";
	}

	@Override
	public String getUsername() {
		return (String)response.get("email");
	}

	@Override
	public String getNickname() {
		return (String)response.get("name");
	}

	@Override
	public String getEmail() {
		return (String)response.get("email");
	}

	@Override
	public User toEntity() {

		return User.builder()
					.username(getUsername())
					.email(getEmail())
					.nickname(getNickname())
					.role(Role.SOCIAL)
					.build();
	}
}

에러

로그인하면 정상적으로 로그인은 되지만 리다이렉트 되지 않고 에러가 발생

"status":999,"error":"None","message":"No message available"}
  • 로그인할 때 정적리소스를 찾지못하면 에러로 핸들링하는거 같다고 함...
    아직도 원인을 잘 모르겠다.

해결

시큐리티 config 에서 웹 이그노잉에

	/* static 관련 인증 설정 무시 */
	@Override
	public void configure(WebSecurity web) throws Exception{
		web
			.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/error");
	}

"/error" 추가

https://www.inflearn.com/questions/31659

네이버 로그인 시 nickname 의 값이 넘어오지 않음

  • attributes.get(response).keySet() 해서 넘어온 정보들을 살펴보았다.

    application.yml에서 scope에 nickname을 추가했음에도 불구하고 nickname이 넘어오지 않았다.
    쓰면서 갑자기 생각이 났다.. 문제 원인은 간단했다!!

    별명을 체크하지 않았기 때문!!!!
    꼼꼼한 개발자가 되자...

해결

  • 네이버 개발자 센터에서 '별명' 권한을 필수로 체크해준다.

0개의 댓글