강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

유요한님의 프로필 이미지
유요한

작성한 질문수

배포후 소셜로그인 에러

작성

·

687

0

스프링부트 REST + OAuth2 + JWT를 사용하고 있는 상황인데

로컬에서는 잘 돌아갑니다.

로컬:

<a id="google-login" href="/oauth2/authorization/google">구글 로그인</a>
<a id="naver-login" href="/oauth2/authorization/naver">네이버 로그인</a>

@Service

@Log4j2

@RequiredArgsConstructor

public class PrincipalOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final MemberRepository memberRepository;

    private final JwtProvider jwtProvider;

    private final TokenRepository tokenRepository;

    @Override

    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        // userRequest.getClientRegistration()은 인증 및 인가된 사용자 정보를 가져오는

        // Spring Security에서 제공하는 메서드입니다.

        ClientRegistration clientRegistration = userRequest.getClientRegistration();

        log.info("clientRegistration : " + clientRegistration);

        // 소셜 로그인 accessToken

        String socialAccessToken = userRequest.getAccessToken().getTokenValue();

        log.info("소셜 로그인 accessToken : " + socialAccessToken);

        OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService =

                new DefaultOAuth2UserService();

        log.info("oAuth2UserService : " + oAuth2UserService);

        // 소셜 로그인한 유저정보를 가져온다.

        OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest);

        log.info("oAuth2User : " + oAuth2User);

        log.info("getAttribute : " + oAuth2User.getAttributes());

        // 회원가입 강제 진행

        OAuth2UserInfo oAuth2UserInfo = null;

        String registrationId = clientRegistration.getRegistrationId();

        log.info("registrationId : " + registrationId);

        if(registrationId.equals("google")) {

            log.info("구글 로그인");

            oAuth2UserInfo = new GoogleUser(oAuth2User, clientRegistration);

        } else  if(registrationId.equals("naver")) {

            log.info("네이버 로그인");

            oAuth2UserInfo = new NaverUser(oAuth2User, clientRegistration);

        } else {

            log.error("지원하지 않는 소셜 로그인입니다.");

        }

        // 사용자가 로그인한 소셜 서비스를 가지고 옵니다.

        // 예시) google or naver 같은 값을 가질 수 있다.

        String provider = oAuth2UserInfo.getProvider();

        // 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다.

        // 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다.

        String providerId = oAuth2UserInfo.getProviderId();

        String name = oAuth2UserInfo.getName();

        // 사용자의 이메일 주소를 가지고 옵니다.

        // 소셜 서비스에서 제공하는 이메일 정보를 사용합니다.

        String email = oAuth2UserInfo.getEmail();

        // 소셜 로그인의 경우 무조건 USER 등급으로 고정이다.

        Role role = Role.USER;

        MemberEntity findUser = memberRepository.findByEmail(email);

        if(findUser == null) {

            log.info("소셜 로그인이 최초입니다.");

            log.info("소셜 로그인 자동 회원가입을 진행합니다.");

            findUser = MemberEntity.builder()

                    .email(email)

                    .memberName(name)

                    .provider(provider)

                    .providerId(providerId)

                    .memberRole(role)

                    .nickName(name)

                    .build();

            log.info("member : " + findUser);

             findUser = memberRepository.save(findUser);

        } else {

            log.info("로그인을 이미 한적이 있습니다.");

        }

        // 권한 가져오기

        List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser);

        // 토큰 생성

        TokenDTO tokenForOAuth2 = jwtProvider.createTokenForOAuth2(email, authorities, findUser.getMemberId());

        // 기존에 이 토큰이 있는지 확인

        TokenEntity findToken = tokenRepository.findByMemberEmail(tokenForOAuth2.getMemberEmail());

        TokenEntity saveToken;

        // 기존의 토큰이 없다면 새로 만들어준다.

        if(findToken == null) {

            TokenEntity tokenEntity = TokenEntity.tokenEntity(tokenForOAuth2);

            saveToken = tokenRepository.save(tokenEntity);

            log.info("token : " + saveToken);

        } else {

            // 기존의 토큰이 있다면 업데이트 해준다.

            tokenForOAuth2 = TokenDTO.builder()

                    .grantType(tokenForOAuth2.getGrantType())

                    .accessToken(tokenForOAuth2.getAccessToken())

                    .accessTokenTime(tokenForOAuth2.getAccessTokenTime())

                    .refreshToken(tokenForOAuth2.getRefreshToken())

                    .refreshTokenTime(tokenForOAuth2.getRefreshTokenTime())

                    .memberEmail(tokenForOAuth2.getMemberEmail())

                    .memberId(tokenForOAuth2.getMemberId())

                    .build();

            TokenEntity tokenEntity = TokenEntity.updateToken(findToken.getId(), tokenForOAuth2);

            saveToken = tokenRepository.save(tokenEntity);

            log.info("token : " + saveToken);

        }

        // 토큰이 제대로 되어 있나 검증

        if(StringUtils.hasText(saveToken.getAccessToken())

                && jwtProvider.validateToken(saveToken.getAccessToken())) {

            Authentication authenticationToken = jwtProvider.getAuthentication(saveToken.getAccessToken());

            log.info("authentication : " + authenticationToken);

            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            UserDetails userDetails = new User(email, "", authorities);

            log.info("userDetails : " + userDetails);

            Authentication authenticationUser =

                    new UsernamePasswordAuthenticationToken(userDetails, authorities);

            log.info("authentication1 : " + authenticationUser);

            SecurityContextHolder.getContext().setAuthentication(authenticationUser);

        } else {

            log.info("검증 실패");

        }

        // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성

        // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다.

        PrincipalDetails principalDetails = new PrincipalDetails(findUser, oAuth2User.getAttributes());

        log.info("principalDetails : " + principalDetails);

        return principalDetails;

    }

    // 권한 가져오기 로직

    private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity findUser) {

        Role role = findUser.getMemberRole();

        List<GrantedAuthority> authorities = new ArrayList<>();

        authorities.add(new SimpleGrantedAuthority("ROLE_" + role.name()));

        log.info("권한 : " + role.name());

        return authorities;

    }

}

@Log4j2

@RequiredArgsConstructor

@Component

public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {

    private final MemberRepository memberRepository;

    private final TokenRepository tokenRepository;

    // Jackson ObjectMapper를 주입합니다.

    private final ObjectMapper objectMapper;

    @Override

    public void onAuthenticationSuccess(HttpServletRequest request,

                                        HttpServletResponse response,

                                        Authentication authentication) throws IOException, ServletException {

        try {

            log.info("OAuth2 Login 성공!");

            // 소셜 로그인 이메일 가져오기

            String email = authentication.getName();

            log.info("email : " + email);

            // 토큰 조회

            TokenEntity findToken = tokenRepository.findByMemberEmail(email);

            log.info("token : " + findToken);

            // 토큰 DTO 반환

            TokenDTO tokenDTO = TokenDTO.toTokenDTO(findToken);

            // 회원 조회

            MemberEntity findUser = memberRepository.findByEmail(email);

            // 회원 DTO 반환

            ResponseMemberDTO memberDTO = ResponseMemberDTO.socialMember(findUser);

            // 헤더에 담아준다.

            response.addHeader("email", memberDTO.getEmail());

            // 바디에 담아준다.

            Map<String, Object> responseBody = new HashMap<>();

            responseBody.put("providerId", memberDTO.getProviderId());

            responseBody.put("provider", memberDTO.getProvider());

            responseBody.put("accessToken", tokenDTO.getAccessToken());

            responseBody.put("refreshToken", tokenDTO.getRefreshToken());

            responseBody.put("email", tokenDTO.getMemberEmail());

            responseBody.put("memberId", tokenDTO.getMemberId());

            responseBody.put("grantType", tokenDTO.getGrantType());

            // JSON 응답 전송

            response.setContentType("application/json");

            response.setCharacterEncoding("UTF-8");

            response.getWriter().write(objectMapper.writeValueAsString(responseBody));

        } catch (Exception e) {

            // 예외가 발생하면 클라이언트에게 오류 응답을 반환

            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

            response.getWriter().write("OAuth 2.0 로그인 성공 후 오류 발생: " + e.getMessage());

            response.getWriter().flush();

        }

    }

}

로컬에서 소셜로그인이 성공하면 OAuth2SuccessHandler에서 바로 JSON으로 반환해주는 형태입니다. 즉, 컨트롤러가 딱히 무슨 역할을 하지 않아도 바로 반환을 해줍니다.

하지만 EC2에 배포하고 구글 개발자 센터, yml에 EC2 퍼블릭을 제대로 입력해주고

로그인한 결과 아이디들 제대로 뜨는데 로그인할 아이디를 클릭을 하면

Whitelabel Error Page 404페이지가 뜹니다. 로컬 코드를 그대로 배포한건데 왜 안될까요?

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.

유요한님께서 작성하신 로컬 환경에서 잘 동작하는 소셜 로그인 코드가 EC2 환경에서 404 에러 페이지가 나타나는 문제를 겪고 계시군요.

 

EC2 환경에서의 문제를 해결하려면 몇 가지 확인해볼 사항이 있습니다.

1. EC2의 보안 그룹 설정 확인: EC2 인스턴스에 지정된 보안 그룹에서 HTTP나 HTTPS 트래픽이 허용되었는지 확인해야 합니다.

2. 올바른 URL과 포트 사용: EC2에 배포한 애플리케이션이 사용하는 포트(기본값 8080)가 올바르게 expose되고 있는지, URL이 정확한지 확인해 보세요.

3. 애플리케이션 로그: Whitelabel Error Page 404 페이지는 특정 경로로의 요청이 처리되지 못했을 때 나타납니다. 로그 파일을 확인하여 정확히 어떤 경로가 문제인지, 예외가 발생했다면 그 내용을 확인해보세요.

4. 구성 파일 확인: application.yml 또는 다른 구성 파일에서 올바른 callback URL과 허용된 리디렉션 URI가 설정되어 있는지 확인하십시오.

5. DNS 설정: 도메인 이름을 사용하고 있다면 DNS 설정이 올바르게 구성되어 EC2 인스턴스를 가리키고 있는지 확인하세요.

6. 스프링 부트 배포 설정: 스프링 부트 애플리케이션이 외부 설정 파일을 제대로 로드하고 있는지, 프로파일 설정이 적용되고 있는지 검토해보세요.

 

아래 Spring Boot 문서에 따르면 소셜 로그인 설정에 관련된 보다 구체적인 정보를 찾을 수 있을 것입니다.

OAuth2에 대해서는 여기를 참고하실 수 있습니다.

에러 페이지 관리에 관련된 내용은 Spring Boot의 Error Handling 부분에서 볼 수 있습니다.

만약 애플리케이션이 커스텀 에러 페이지를 정상적으로 처리하지 못할 경우 이 부분을 확인하여 점검하는 것이 좋습니다.

추가적인 문제 해결을 위해, 서버에서 발생하는 로그를 상세히 살펴보고 설정한 구성이 모두 올바르게 적용되었는지 다시 한번 확인하시길 권장합니다.

감사합니다.

답변이 수정되었습니다. 확인해주시면 감사하겠습니다!

유요한님의 프로필 이미지
유요한

작성한 질문수

질문하기