Spring Security

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

2023. 3. 26. 18:02

이 정보는

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에 저장된다.

'Spring Security' 카테고리의 다른 글

[OAuth] 네이버 로그인  (0) 2023.03.27
[OAuth] 페이스북 로그인  (0) 2023.03.26
[OAuth] Authentication객체가 가질 수 있는 2가지 타입  (0) 2023.03.26
[OAuth] 구글 회원 프로필 정보 받아보기  (0) 2023.03.26
[OAuth] 구글 로그인 준비  (0) 2023.03.26
'Spring Security' 카테고리의 다른 글
  • [OAuth] 네이버 로그인
  • [OAuth] 페이스북 로그인
  • [OAuth] Authentication객체가 가질 수 있는 2가지 타입
  • [OAuth] 구글 회원 프로필 정보 받아보기
ewok
ewok
ewok
기록장
ewok
전체
오늘
어제
  • 분류 전체보기
    • 웹개발 교육
      • HTML
      • CSS
      • JavaScript
      • Database
      • Java
      • jQuery
      • Ajax
      • Bootstrap
      • jsp
      • Spring
      • MyBatis
      • 프로젝트
    • JAVA
    • SpringBoot
      • 기초
      • AWS
      • 개인프로젝트
    • Spring Security
    • JPA
    • 테스트코드
    • Error
    • CS
      • 컴퓨터 구조
      • 이산수학
    • 알고리즘
      • 정리
      • Java
    • SQL
    • 자격증
      • SQLD
      • 정보처리기사
    • Git

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • branch
  • org.springframework.beans.factory.UnsatisfiedDependencyException
  • git bash
  • this
  • 브랜치
  • 노랭이
  • SQLD
  • merge commit
  • GIT
  • 버전 관리
  • base
  • 생성자
  • sqld 합격
  • org.hibernate.tool.schema.spi.CommandAcceptanceException
  • sqld 자격증

최근 댓글

최근 글

hELLO · Designed By 정상우.
ewok
[OAuth] 구글 로그인 및 자동 회원가입 진행 완료
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.