2023 여름 모각코 - 절개와지조

[모각코 / 230822] spring security OAuth 로그인 구현하기

pkyung 2023. 8. 19. 22:28
반응형

 

안녕하세요 모각코 6일차입니다. 

 

 

오늘은 구글 로그인을 진행해보았습니다. 

 

 

구글 클라우드 플랫폼에 들어갑니다. 

https://console.cloud.google.com/apis 

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

 

새 프로젝트를 생성해줍니다. 

저는 springboot-oauth-google로 만들었습니다. 

 

그리고 OAuth 동의 화면에 들어가서 외부로 열어주고 URI를 http://localhost:8080/login/oauth2/code/google 를 입력합니다. 구글 로그인을 진행한 후 코드를 받게 되는 url 입니다. 

 

 

그리고 사용자 인증 정보에서 사용자 인증 정보를 만들어줍니다. 

 

 

 

만들어진 clientId를 applicaion.yml 파일에 추가해줍니다. 

spring:  
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 클라이언트 id
            client-secret: 클라이언트 pwd
            scope:
              - email
              - profile

 

 

이제 구글 로그인을 위한 준비를 마쳤습니다. 

 

 

파일 구조는 다음과 같습니다. 

 

loginForm.html 파일에 구글 로그인 주소를 입력합니다.

/oauth2/authorization/google 은 구글 로그인을 진행하는 정해져있는 url로 오타를 주의해야합니다. 

<a href="/oauth2/authorization/google">구글 로그인</a>

 

SecurityConfig.java 파일에 이를 추가해줍니다. 

OAuth2를 진행할 때는

1. 코드 받기 (인증)

2. 엑세스 토큰 받기 (권한)

3. 사용자 프로필 정보를 가져온다

4-1. 그 정보를 토대로 회원가입 자동으로 진행

4-2. 이메일, 전화번호, 이름, 아이디 필요 시 추가적인 회원가입 필요

과정이 있는데 구글 로그인의 경우에는 로그인  완료 후에 엑세스 토큰과 사용자 프로필 정보를 바로 받게 됩니다. 

그래서 그 후처리 과정을 principalOauth2UserService를 통해 진행하려 합니다. 

.and()
.oauth2Login()
.loginPage("/loginForm")  // 구글 로그인이 완료된 후 엑세스 토큰 + 사용자 프로필 정보 받음 코드X
.userInfoEndpoint()
.userService(principalOauth2UserService);

 

 

 

security 안에 session 에는 authentication 타입이 들어갈 수 있습니다. 

그 안에서 일반 로그인 사용자의 경우 UserDetails, OAuth 사용자의 경우 OAuth2User 를 갖게 되는데 이 두 개를 편하게 관리하기 위해 PrincipalDetails에 UserDetails와 OAuth2User를 둘 다 상속받습니다. 

public class PrincipalDetails implements UserDetails, OAuth2User {}

 

PrincipalOauth2UserService.java 파일입니다. 

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {


    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserRepository userRepository;

    // 구글로부터 받은 userRequest data 에 대해 후처리되는 함수
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2User oAuth2User = super.loadUser(userRequest);

        String provider = userRequest.getClientRegistration().getClientId();
        String providerId = oAuth2User.getAttribute("sub");
        String username = provider + "_" + providerId;
        String email = oAuth2User.getAttribute("email");
        String password = bCryptPasswordEncoder.encode("겟인데어");
        String role = "ROLE_USER";

        User user = userRepository.findByUsername(username);

        if (user == null) {
            userRepository.save(User.builder()
                    .username(username)
                    .email(email)
                    .password(password)
                    .role(role)
                    .provider(provider)
                    .providerId(providerId)
                    .build());
        } else {
            System.out.println("구글 로그인을 한 적이 있습니다");
        }

        return new PrincipalDetails(user, oAuth2User.getAttributes());
    }
}

 

이제 일반 유저나 OAuth 유저 모두 PrincipalDetails로 정보를 받게 됩니다. 

@GetMapping("/user")
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
    return "user";
}

 

 

그런데 여기서 빈 순환 참조의 문제가 발생하게 됩니다. 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  securityConfig (field private com.example.security.config.oauth.PrincipalOauth2UserService com.example.security.config.SecurityConfig.principalOauth2UserService)
↑     ↓
|  principalOauth2UserService (field private org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder com.example.security.config.oauth.PrincipalOauth2UserService.bCryptPasswordEncoder)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.


Process finished with exit code 0

 

 

spring은 application context bean 생성 과정에서 생길 수 있는 문제점을 컴파일 과정에서 오류로 감지하기 때문에 @Lazy 어노테이션(지연 로딩)을 통해 해결했습니다. 이는 좋은 방법은 아니고 추후에 프로젝트에서는 좋은 설계로 진행하여 이러한 문제가 나오지 않도록 해야합니다. 

@Autowired
private @Lazy BCryptPasswordEncoder bCryptPasswordEncoder;
반응형