각 서비스 등록하는 방법은 예전에 공부하면서 작성한 게시물에 잘 정리되어있으니 생략한다. 단, 구글에서 범위 추가하는 것은 아래 따로 첨부
http://localhost:8080/login/oauth2/code/google
네이버는 미리 만들어둔 API를 사용했다.
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
...
.and()
.oauth2Login()
.userInfoEndpoint() /* OAuth 2 로그인 성공 후 사용자 정보를 가져올 떄의 설정 담당 */
.userService(customOAuth2UserService); /* 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스 구현체 등록, 사용자 정보를 가져온 상태에서 추가 진행 기능 명시 */
순서를 formLogin -> oauth2Login -> logout 즉,
formlogin()....and().oauth2Login()...and().logout 하면 에러가 난다. 그래서 oauth2Login()을 맨 마지막에 넣었더니 에러가 뜨지 않았다.
- 왜 에러가 뜰까..?😥
/* 소셜 로그인 시 이미 등록된 회원인 경우 수정 날짜 업데이트, 기존 데이터는 보존 */
public User updateModifiedDate() {
this.onPreUpdate(); // 수정 날짜 업데이트
return this;
}
기존에는 닉네임도 업데이트하도록 설계했지만 어차피 회원 설정에서 닉네임도 수정하기 때문에 수정날짜(modifiedDate)만 업데이트하도록 했다.
/* OAuth 로그인 시 중복 체크 */
Optional<User> findByEmail(String email);
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);
}
}
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();
}
}
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