ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OAuth - 구글 로그인(구글 로그인 및 자동 회원가입)
    스프링/스프링 시큐리티 2024. 1. 10. 00:11

    시큐리티 세션에 들어갈 수 있는 객체는 Authentication 객체이다.

     

    Authentication이 담을 수 있는 타입은 OAuth2User와 UserDetails 타입이다.

     

    저 2개의 타입은 User 객체를 가지고 있지 않기 때문에 PrincipalDetials라는 타입을 만들어서 UserDetails를 implement 시켰는데, 여기에 OAuth2User까지 implement한다는 내용이 이전 포스팅이었다.

     

     

    PrincipalDetails.class

    @Data
    public class PrincipalDetails implements UserDetails, OAuth2User {
    
        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;
        }
    
        @Override
        public String getName() {
            return null;
            // 어차피 잘 사용 안하니 null
        }
    }

    기존에 있던 생성자는 로컬 로그인의 생성자이고, 두 번째 생성자를 만들어줬다. user와  attributes를 파라미터로 받는 생성자이다. 오버라이딩 하는 함수들은 getAttributes()는 attributes를 반환해주며 getName()은 잘 사용 안해서 그냥 null로 해뒀다.

     

     

     

    로컬 로그인 유저 : user만 가지고 있음

    구글 로그인 유저 : user, attributes를 가지고 있음

     

     

     

    PrincipalOauth2UserService.class

    @Service
    public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
    
        @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;
    
        @Autowired
        private UserRepository userRepository;
    
        // 구글에서 받은 userRequest 데이터를 후처리하는 함수
        @Override
        public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
            System.out.println("getClientRegistration : " + userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인 했는지 확인 가능
            System.out.println("getAccessToken : " + userRequest.getAccessToken());
    
            OAuth2User oAuth2User = super.loadUser(userRequest);
    
            System.out.println("getAttributes() : " + oAuth2User.getAttributes());
            // 구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인 완료 -> code를 리턴 -> OAuth-client 라이브러리가 코드를 통해 AccessToken 요청
            // Token으로 부터 받아온 userRequest 정보로 회원 프로필을 받아야 하는데, 이때 사용되는 함수가 loadUser 함수
    
            // 회원가입 진행
            String provider = userRequest.getClientRegistration().getRegistrationId(); // google
            String providerId = oAuth2User.getAttribute("sub");
            String username = provider+"_"+providerId;
            String password = bCryptPasswordEncoder.encode("password"); // OAuth 로그인을 하면 password가 필요없긴 함
            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);
            } else {
                // 이미 있으면 save를 안함
            }
    
            return new PrincipalDetails(userEntity, oAuth2User.getAttributes());
        }
    }

    PrincipalOauth2UserService에서 구글 회원의 정보를 가져온다. 가져온 값들을 가지고 builder를 통해 생성자를 userEntity를 만들어 준 다음, 중복되지 않으면 userRepository에 저장을 해서 회원가입을 시켜주는 로직이다.

     

     

     

    @GetMapping("/user")
    public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
        System.out.println("principalDetails : " + principalDetails.getUser());
        return "user";
    }

    컨트롤러에서 확인을 해보기위해 PrincipalDetails 타입으로 로그인을 2번 진행했더니 로컬 로그인, 구글 로그인 둘 다 user 객체가 잘 나오는 것을 확인했다.

     

    이렇게 하면 분리할 수 없이 편하게 잘 사용할 수 있다. 또한, 형변환을 따로 할 필요도 없다.

     

     

     

     

    @AuthenticationPrincipal 어노테이션

    따로 PrincipalDetailsService와 PrincipalOauth2UserService를 만들었는데, 그 이유는 PrincipalDetails로 반환하기 위해서이다. (OAuth로 들어왔을 때 회원가입을 해주는 이유도 있다.)

    이렇게 반환해주면 @AuthenticationPrincipal 객체에 자동으로 return이 되어서 객체가 저장된다.

     

    각각의 Service의 함수가 종료될 때 @AuthenticationPrincipal 어노테이션이 만들어진다.

Designed by Tistory.