요구사항
네이버 로그인과 구글 로그인을 사용하기 때문에 이를 구별 해줄 수 있는 서비스를 구현해야한다.
서비스 구현
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
}
OAuth2UserService 타입을 파라미터로 받고 서비스를 설정하기 때문에 반드시 상속 받아야 한다.
메서드 오버라이딩
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
}
}
유저를 불러오면 판단해야하기 때문에 OAuth2UserService의 메서드인 loadUser를 재정의 해야함. 로그인이 인증이 실패 되어도 동작하는 서비스의 지장을 주면 안되기 때문에 로그인 실패 관련 Exception은 throws함.
대리자 생성
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
}
}
DefaultOAuth를 통해 RestOperations으로 UserInfo 엔드포인트에 사용자 속성을 요청해서 사용자 정보를 가져와야하기
때문에 CustomOAuth2UserService.loadUserd의 동작을 대신 해주는 대리자를 만들었다.
사용자 정보를 담을 Class 정의
@Getter
public class OAuthAttributes {
private Map<String,Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
}
구글, 네이버 콘솔을 가져오기로 설정한 속성값들을 변수로 선언.
@Getter
public class OAuthAttributes {
private Map<String,Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
@Builder
public OAuthAttributes(Map<String,Object> attributes, String nameAttributeKey, String name, String email, String picture){
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public User toEntity(){
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.USER)
.build();
}
}
빌더를 선언하여 Setter를 굳이 사용하지 않고 빌드할수 있게해줌. 데이터 베이스의 저장할 수 있게 User Entity를 리턴해 주는 toEntity를 구현해준다.
@Getter
public class OAuthAttributes {
private Map<String,Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
@Builder
public OAuthAttributes(Map<String,Object> attributes, String nameAttributeKey, String name, String email, String picture){
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
//사용자 정보는 Map이기 때문에 변경해야함
public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String,Object> attributes){
//네이버 로그인 인지 판단.
if("naver".equals(registrationId)){
return ofNaver("id",attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
public User toEntity(){
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.USER)
.build();
}
}
네이버 로그인 인지 아닌지 판단해주는 메서드를 생성.
private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
// 응답 받은 사용자의 정보를 Map형태로 변경.
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
// 미리 정의한 속성으로 빌드.
return OAuthAttributes.builder()
.name((String) response.get("name"))
.email((String) response.get("email"))
.picture((String) response.get("profile_image"))
.attributes(response)
.nameAttributeKey(userNameAttributeName)
.build();
}
public static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String,Object> attributes){
// 미리 정의한 속성으로 빌드.
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.picture((String) attributes.get("picture"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
of 에서 사용되는 메서드를 구현한다. (네이버는 Map<String, Object>형태가 아니기 때문에 따로 타입을 변경해주어야 함)
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//DefaultOAuth2User 서비스를 통해 User 정보를 가져와야 하기 때문에 대리자 생성
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
//네이버 로그인인지 구글로그인인지 서비스를 구분해주는 코드
String registrationId = userRequest.getClientRegistration().getRegistrationId();
//OAuth2 로그인 진행시 키가 되는 필드값 프라이머리키와 같은 값 네이버 카카오 지원 x
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
//OAuth2UserService를 통해 가져온 데이터를 담을 클래스
OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
//로그인 한 유저 정보
User user = saveOrUpdate(attributes);
//httpSession의 유저 속성을 설정
httpSession.setAttribute("user", new SessionUser(user));
// 로그인한 유저를 리턴함
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
//User 저장하고 이미 있는 데이터면 Update
private User saveOrUpdate(OAuthAttributes attributes) {
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}
CustomOAuth2UserService 구현