Spring boot 구글로그인 및 자동 회원가입 마무리
일단 두가지를 먼저 테스팅해볼 예정이다.
1. PrincipalDetails에서의 User정보
@GetMapping("/test/login")
public @ResponseBody String testLogin(
Authentication authentication,
@AuthenticationPrincipal PrincipalDetails userDetails) {
System.out.println("userDetails ============== ");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication: " + principalDetails.getUser()); //user정보를 호출
System.out.println("userDetails: " + userDetails.getUsername());
return "세션 정보 확인하기";
}
2. OAuth2User의 User정보
@GetMapping("/test/oauth/login")
public @ResponseBody String testOauthLogin(
Authentication authentication,
@AuthenticationPrincipal OAuth2User oauth) { //DI(의존성 주입)
System.out.println("Oauth2User ============== ");
OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authentication: " + oauth2User.getAttributes()); //user정보를 호출
System.out.println("oauth2User: " + oauth.getAttributes());
return "세션 정보 확인하기";
}
PrincipalDetails 는 현재 UserDetails를 implements하고 있기 때문에 PrincipalDetails(UserDetails) 가 된다.
위 두가지 컨트롤러를 보면 첫번째 컨트롤러는 우리가 이전에 직접 커스텀해서 회원가입을 진행한 User정보를 가져오는 컨트롤러이다.
해당 URL로 이동하면 콘솔에는 우리가 가입했던 User의 정보가 나타난다.
마찬가지로 두번째 컨트롤러의 URL로 이동하면 구글 로그인시에 구글에서 프로필을 가져와 User정보를 확인할 수 있다.
그렇다면 구글 로그인시에 User정보와 일반 로그인시의 User정보 둘 다 가져오고 싶으면 어떻게 해야될까
아래 그림으로 쉽게 해결할 수 있다.
일반 로그인시 User정보 호출과 구글 로그인시 User정보 호출
Security Session내에 Authentication에는 두가지 정보를 포함시킬 수 있다 Oauth2User라는 oauth로그인과 UserDetails라는 일반 로그인이다.
UserDetails의 경우 우리가 PrincipalDetails라는 클래스를 생성해 해당 클래스에 implements하여 user정보를 주입해주는 방식을 사용했다.
그래서 userDetails의 loadUserByUsername이 호출될 때 PrincipalDetails가 Authentication내로 들어가게 된다.
SpringSecurity(Authentication(PrincipalDetails(UserDetails))) << 이런 모양이다.
그렇다면 구글 로그인을 하려면 위와 같은 방식으로 똑같이 만들어야 한다.
SpringSecurity(Authentication(OAuth2User)) << 이런 모양이다.
그러나 사용자 입장에서 직접 회원가입을 할지, 구글 로그인 등의 OAuth로그인을 이용할 지 모르기 때문에 두 가지를 같이
관리해줘야 한다.
위와 같이 따로 만들게 되면 꽤나 복잡한 방식을 가지기 때문에 이를 쉽게 해결하기 위해선 PrincipalDetails에 UserDetails와 OAuth2User를 함께 implements해주면 해결이 된다.
UserDetails와 OAuth2User를 PrincipalDetails에 같이 implements 해줌
@Data
public class PrincipalDetails implements UserDetails, OAuth2User{
private static final long serialVersionUID = 1L;
private User user; // 컴포지션
//생성자
public PrincipalDetails(User user) {
this.user = user;
}
//해당 User의 권한을 리턴해주는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
//해당 User 의 password
@Override
public String getPassword() {
return user.getPassword();
}
//해당 User의 username
@Override
public String getUsername() {
return user.getUsername();
}
/*
* 계정 만료 여부
* true: 만료 안됨
* false: 만료
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/*
* 계정 잠김 여부
* true: 만료 안됨
* flase: 만료
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/*
* 비밀번호 만료 여부
* true: 만료 안됨
* false: 만료
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/*
* 사용자 활성화
* true: 활성화
* false: 비활성화
*/
@Override
public boolean isEnabled() {
return true;
}
/////////////////ouath2User를 implements해서 새로 overide해준 내용//////////////////
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getName() {
return null;
}
}
그래서 오버라이드를 해주면 getAttributes()와 getName()이 나온다.
getAttributes를 중점적으로 다룰 건데 이는 받는 값이 Map<String, Object> 이다.
이는 oauth2User.getattributes의 타입과 같다.
이를 이용해 먼저 아래와 같이 생성자를 만들어주자
PrincipalDetails 생성자 생성 및 return
public class PrincipalDetails implements UserDetails, OAuth2User{
private static final long serialVersionUID = 1L;
private User user; // 컴포지션
private Map<String, Object> attributes;
//생성자(일반 로그인)
public PrincipalDetails(User user) {
this.user = user;
}
//생성자(OAuth 로그인)
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
}
위와같이 만들어서 Oauth로그인 시에는 user 랑 attributes를 함께 보내서 강제로 회원가입 시키도록 만들 수 있다.
강제 회원가입의 편의를 위해 User 클래스에 builder를 이용해주도록 하자
User 클래스
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id //PrimaryKey
@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; //java.sql import
}
PrincipalOauth2UserService 부분
@Service
@RequiredArgsConstructor
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final UserRepository userRepository;
// 구글로 부터 받은 userRequest 데이터에 대한 후처리 되는 함수
// 함수종료시 @Authentication 어노테이션이 만들어진다.
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequest: " + userRequest);
System.out.println("getAccessToken: " + userRequest.getAccessToken());
System.out.println("getAccessTokenValue: " + userRequest.getAccessToken().getTokenValue());
System.out.println("getClientRegistration: " + userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인 했는지 확인 가능
OAuth2User oauth2User = super.loadUser(userRequest);
System.out.println("getAttributes: " + oauth2User.getAttributes());
String provider = userRequest.getClientRegistration().getRegistrationId(); // google
String providerId = oauth2User.getAttribute("sub");
String username = provider + "_" + providerId; //google_100194700249082101266
String password = bCryptPasswordEncoder.encode("dochi");
String email = oauth2User.getAttribute("email");
String role = "ROLE_USER";
User userEntity = userRepository.findByUsername(username);
if(userEntity == null) {
System.out.println("Welcome The First Join Membership");
userEntity = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
}else {
System.out.println("This Id already exists");
}
//회원가입을 강제로 진행
return new PrincipalDetails(userEntity, oauth2User.getAttributes());
}
}
userEntity가 null이면 아직 구글로 회원가입을 하지 않은상태이기 때문에 구글에서 받아온 정보들을 builder에 담아서 return해준다
그리고 마지막으로 컨트롤러에서 잘 회원가입이 됐는지 확인해주면 된다.
Controller
@GetMapping("/user") //@ResponseBody 는 페이지를 만들지 않고 해당 문자만 return해서 테스팅하기위해 사용
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("principalDetails: " + principalDetails.getUser());
return "user";
}
일반 로그인시
구글 로그인시
같은 구글 아이디로 로그인했을 경우