이번에는 소셜 로그인 코드를 작성해보도록 하겠습니다.
소셜 로그인을 위한 설정은 많은 블로그에서 많이 다루고 있어서 생략 하도록 하겠습니다.
소셜 로그인을 사용하기 위하여 build.gradle에 추가를 해줍니다.
build.gradle
// Oauth2.0
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
소셜 로그인을 사용하기 위하여 OAuth2Attribute와 OAuth2UserService<OAuth2UserRequest, OAuth2User>를 상속받은 CustomOAuth2UserService를 작성하도록 하겠습니다.
OAuth2Attribute.java
@Getter
public class OAuth2Attribute {
// OAuth2에서 받아온 속성 정보
private Map<String, Object> attributes;
private String email;
private String name;
private String profileImgUrl;
private String provider;
private String nameAttributeKey;
@Builder
public OAuth2Attribute(Map<String, Object> attributes, String email, String name, String profileImgUrl, String provider, String nameAttributeKey) {
this.attributes = attributes;
this.email = email;
this.name = name;
this.profileImgUrl = profileImgUrl;
this.provider = provider;
this.nameAttributeKey = nameAttributeKey;
}
// OAuth2 속성을 바탕으로 OAuth2Attribute 객체를 생성하는 정적 팩토리 메서드
public static OAuth2Attribute of(String provider, String userNameAttributeName, Map<String, Object> attributes) {
switch (provider) {
case "google":
return ofGoogle(provider, userNameAttributeName, attributes);
case "naver":
return ofNaver(provider, "id", attributes);
case "kakao":
return ofKakao(provider, "email", attributes);
default:
throw new DoNotSearchProviderException(ErrorCode.DO_NOT_SEARCH_PROVIDER);
}
}
// Google OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
private static OAuth2Attribute ofGoogle(String provider, String userNameAttributeName, Map<String, Object> attributes) {
return OAuth2Attribute.builder()
.provider(provider)
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.profileImgUrl((String) attributes.get("picture"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
// Naver OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
private static OAuth2Attribute ofNaver(String provider, String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return OAuth2Attribute.builder()
.provider(provider)
.name((String) response.get("name"))
.email((String) response.get("email"))
.profileImgUrl((String) response.get("profile_image"))
.attributes(response)
.nameAttributeKey(userNameAttributeName)
.build();
}
// Kakao OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
private static OAuth2Attribute ofKakao(String provider, String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> kakaoProfile = (Map<String, Object>) kakaoAccount.get("profile");
return OAuth2Attribute.builder()
.provider(provider)
.name((String) kakaoProfile.get("nickname"))
.email((String) kakaoAccount.get("email"))
.profileImgUrl((String) kakaoProfile.get("profile_image_url"))
.attributes(kakaoAccount)
.nameAttributeKey(userNameAttributeName)
.build();
}
// OAuth2Attribute를 기반으로 User 엔티티로 변환
public User toOAuth2UserEntity() {
return User.builder()
.email(email)
.name(name)
.profileImgUrl(profileImgUrl)
.role(Role.GUEST)
.provider(provider)
.build();
}
}
여기서 가장 헷갈렸던 부분이 google은 get으로 바로 정보를 가져올 수 있지만 naver와 kakao는 한번 아니면 두번 벗겨내서 정보를 가져와야하고 naver는 response로 값을 받아와야하고 kakao는 kakaoAccount, kakaoAccount 내부의 kakaoProfile에서 정보를 가져와야 한다는 점이였습니다.
CustomOAuth2UserService.java
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
@Transactional
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// OAuth2UserService의 구현체를 가져오기 위해 DefaultOAuth2UserService를 사용합니다.
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate =
new DefaultOAuth2UserService();
// OAuth2UserRequest를 사용하여 OAuth2UserService를 호출하여 OAuth2User 정보를 가져옵니다.
OAuth2User oAuth2User = delegate.loadUser(userRequest);
// OAuth2 공급자와 사용자 이름 속성을 가져옵니다.
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttribute = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
// OAuth2User에서 속성을 추출하고, OAuth2Attribute 객체로 변환합니다.
OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(registrationId, userNameAttribute, oAuth2User.getAttributes());
// OAuth2Attribute를 사용하여 사용자를 저장하거나 업데이트합니다.
User user = saveOrUpdate(oAuth2Attribute);
// 사용자 권한과 속성을 포함한 DefaultOAuth2User 객체를 생성하여 반환합니다.
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(
user.getRoleKey()))
, oAuth2Attribute.getAttributes()
, oAuth2Attribute.getNameAttributeKey());
}
// OAuth2Attribute를 사용하여 사용자 엔티티를 저장 또는 업데이트합니다.
private User saveOrUpdate(OAuth2Attribute oAuth2Attribute) {
User user = userRepository.findByEmail(oAuth2Attribute.getEmail())
.map(entity -> entity.update(oAuth2Attribute.getName(), oAuth2Attribute.getProfileImgUrl()))
.orElse(oAuth2Attribute.toOAuth2UserEntity());
return userRepository.save(user);
}
}
설명은 주석을 참고 해주시면 됩니다. 저는 제공되는 정보만 사용하기 위해 따로 OAuth2User를 만들지 않았지만 다른 정보가 필요하다면 CustomOAuth2User를 만들어서 사용하셔도 무방합니다.
여기서 볼게 saveOrUpdate 입니다. saveOrUpdate로 로그인을 할 때 사용자가 이름과, 프로필을 바꿨다면 바뀐 값으로 업데이트 되게 만들었습니다.
User.java
public User update(String name, String profileImgUrl) {
this.name = name;
this.profileImgUrl = profileImgUrl;
return this;
}
다음 메소드를 도메인에 정의하여 사용했습니다.
SecurityConfig.java
.and()
.oauth2Login()
.userInfoEndpoint().userService(customOAuth2UserService);
다음과 같이 SecurityConfig 파일에 autenticationEntryPoint 아래 부분에 넣어주시면 됩니다.
successHandler나 failureHandler를 설정하여 로그인 성공시 이동, 로그인 실패시 이동을 설정할 수 있는데 이 부분은 아직 저도 학습하고 있는 부분이라 기회가 된다면 코드를 올려보도록 하겠습니다
[Spring Boot] 회원 가입 & 로그인(4)
이번엔 Security 설정과 Token에 대해 설정하려고 합니다. 먼저 패스워드 암호화를 적용하기 위한 Config 파일을 만들었습니다. PasswordEncoderConfig.java @Configuration public class PasswordEncoderConfig { @Bean public Pa
classruntime.tistory.com
[Spring Boot] 회원 가입 & 로그인(3)
로그인을 하기 위해 JWT 토큰을 사용하기로 결정 했습니다. 그래서 그전에 JWT 토큰이 무엇인지 어떻게 사용되는 것인지 먼저 간단하게 알아보려고 합니다. JWT(JSON Web Token)란 인증에 필요한 정보
classruntime.tistory.com
[Spring Boot]회원 가입 & 로그인(2)
이번엔 도메인 설정 이후 컨트롤러와 서비스를 만들어 보도록 하겠습니다. 먼저 반환 형식을 맞추려고 합니다. Response { "status" : "success", "message" : "회원 가입 성공", "data" : { "userId" : 1 } } 위와 같
classruntime.tistory.com
[Spring Boot]회원 가입 & 로그인 (1)
기본 회원 가입 및 로그인을 구현하면서 소셜 로그인도 같이 병합해서 사용해야겠다는 생각이 들어 만들어 봤습니다. ERD는 쇼핑몰을 생각하면서 만들어 봤습니다. 먼저 기본 회원 가입 ERD 입니
classruntime.tistory.com
GitHub - Llimy1/Auth_Spring
Contribute to Llimy1/Auth_Spring development by creating an account on GitHub.
github.com
'Backend > Spring' 카테고리의 다른 글
[Spring Boot] 회원 가입 & 로그인(4) (1) | 2023.10.04 |
---|---|
[Spring Boot] 회원 가입 & 로그인(3) (0) | 2023.10.04 |
[Spring Boot]회원 가입 & 로그인(2) (1) | 2023.09.26 |
[Spring Boot]회원 가입 & 로그인 (1) (0) | 2023.09.26 |