[OAuth] 구글 로그인 및 자동 회원가입 진행 완료

이 정보는

Map<String, Object> 형태로 들어오게 된다.
이 정보를 PrincipalDetails에 넣기 위해
@Data
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails, OAuth2User {
//일반 로그인
private final User user; //콤포지션
// OAuth 로그인
private final Map<String, Object> attributes;
...
// OAuth2User 오버라이딩
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return null; // 사용하지 않을 함수라 null로 두었다.
}
}
PrincipalOauth2UserService
package com.cos.security.config.oauth;
import com.cos.security.config.auth.PrincipalDetails;
import com.cos.security.model.User;
import com.cos.security.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final UserRepository userRepository;
// 로그인 후처리
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequest : " + userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인 했는지 확인 가능
System.out.println("userRequest : " + userRequest.getAccessToken().getTokenValue());
OAuth2User oAuth2User = super.loadUser(userRequest);
// 구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인 완료 -> code를 리턴(OAuth-Client라이브러리) -> AccessToken 요청
// userRequest 정보 -> loadUser함수 -> 구글로부터 회원프로필 받아준다.
System.out.println("getAttribute : " + oAuth2User.getAttributes());
// 회원가입을 강제로 진행
String provider = userRequest.getClientRegistration().getRegistrationId(); // google
String providerId = oAuth2User.getAttribute("sub");
String username = provider + "_" + providerId;
String password = bCryptPasswordEncoder.encode("아무거나");
String email = oAuth2User.getAttribute("email");
String role = "ROLE_USER";
User userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
userEntity = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
}
return new PrincipalDetails(userEntity, oAuth2User.getAttributes());
}
}
User
package com.cos.security.model;
import java.sql.Timestamp;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Data;
@Data
@NoArgsConstructor
@Entity
public class User {
@Builder
public User(String username, String password, String email, String role, String provider, String providerId, Timestamp createDate) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
this.provider = provider;
this.providerId = providerId;
this.createDate = createDate;
}
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role; //ROLE_USER, ROLE_ADMIN
private String provider;
private String providerId;
@CreationTimestamp
private Timestamp createDate;
}
여기서 PrincipalDetailsService에서
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if (userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
PrincipalDetails에 생성자 매개변수로 User 타입 하나만 들어있어서 PrincipalDetails를 아래와 같이 바꾼다.
@Data
@NoArgsConstructor
public class PrincipalDetails implements UserDetails, OAuth2User {
// 일반 로그인
private User user; //콤포지션
//OAuth 로그인
private Map<String, Object> attributes;
public PrincipalDetails(User user) {
this.user = user;
}
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
IndexController
@GetMapping("/user")
@ResponseBody
public String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("principalDetails : " + principalDetails.getUser());
return "user";
}
재시작해보면 아래와 같이 에러가 발생한다.

https://inflearn.com/questions/627033
순환 의존관계오류나신 분들 보세욥! - 인프런 | 질문 & 답변
@Component public class CustomBCryptPasswordEncoder extends BCryptPasswordEncoder { } 위 처럼 따로 BcryptPasswordEncoder를 만들어주고 컨테이너에 등록합니다! //해당 메서...
www.inflearn.com
해결했다면 일반 사용자인 ssar로 로그인한다. 그 후 /user로 접속한다.

이번에는 구글로 로그인하자

일반 로그인과 OAuth 로그인을 분리하지 않아도 잘 작동된다.
@GetMapping("/user")
@ResponseBody
public String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("principalDetails : " + principalDetails.getUser());
return "user";
}
@AuthenticationPrincipal가 활성화되는 시점에 대해 알아보면
PrincipalDetailsService와 PrincipalOauth2UserService를 만들었는데 이 두 클래스를 만들지 않아도 기본적으로 loadUser()와 loadUserByUsername()은 발동을 한다. 그리고 이 함수들이 종료될 때 @AuthenticationPrincipal 어노테이션이 만들어진다.


그럼에도 굳이 Service를 만드는 이유는 PrincipalDetails 타입을 리턴하기 위해서이다.
리턴된 PrincipalDetails는 시큐리티 세션의 Authentication에 저장된다.