스프링부트와 AWS로 혼자 구현하는 웹서비스 따라하기


어제는 세션 저장소를 데이터 베이스로 옮겨봤다.
오늘은 네이버 로그인 API 를 이용해 네이버로그인도 가능하도록 해볼테다

시작 👊

네이버 로그인 API 만들기

네이버 개발자 센터에서 로그인 API 서비스에 등록

  1. 네이버 로그인 API 웹 사이트에 들어가서

  2. 오픈 API 이용신청을 하자
    애플리케이션 이름은 알아먹을 수 있는 거 하면되고,
    사용 API 에 선택하세요는 그냥 두고 회원이름, 이메일 주소, 프로필 사진 에 체크

  3. 등록하면 Client ID, Client Secret 가 나온다.

이제 구글 로그인 API 처럼 ClientID 와 ClientSecret 을 등록하면 될 것 같지만.
naver는 스프링 시큐리티를 공식 지원하지 않기 때문에 그동안 CommonOAuth2Provider 에서 해주던 값들도 전부 수동으로 입력해야합니다.

ClientId, ClientSecret 등록

  1. application-oauth.properties 에 등록합니다.
# application-oauth.properties

...

# registration
spring.security.oauth2.client.registration.naver.client-id=클라이언트아이디
spring.security.oauth2.client.registration.naver.client-secret=클라이언트비밀
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization_grant_type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver

# provider
spring.security.oauth2.client.provider.naver.authorization_uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token_uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user_name_attribute=response

user_name_attribute=response : 기준이 되는 user_name 의 이름을 네이버에서는 response 로 해야합니다. 왜냐면 네이버의 회원 조회 시 반환되는 JSON 형태 때문입니다.

스프링 시큐리티에선 하위 필드를 명시할 수 없습니다. 최상위 필드들만 user_name으로 지정 가능합니다. 하지만 네이버의 응답값 최상위 필드는 resultCode, message, response 입니다.
그래서 스프링 시큐리티에서 인식 가능한 필드는 저 세 가지 중에서 골로야합니다. 우리는 responseuser_name 으로 지정하고 이후 자바 코드로 response 의 id 를 user_name 으로 지정하겠습니다.

스프링 시큐리티 설정 등록

구글 로그인을 등록하면서 대부분 코드가 확장성 있게 작성되었다 보니 네이버는 쉽게 등록 가능합니다.

오.. 멋있습니다.

OAuthAttributes 에 네이버인지 판단하는 코드와 네이버 생성자만 추가해주면 됩니다.

오... ㄱㄱ

  1. config.auth.dto 에 있는 OAuthAttributes 에 코드를 추가해준다.
// OAuthAttributes.java

	...
    
        public static OAuthAttributes of(String registrationId,
                                     String userNameAttributeName,
                                     Map<String, Object> attributes){
        // if 문으로 naver 판단 코드 추가
        if("naver".equals(registrationId)){
            return ofNaver("id", attributes);
        }

        return ofGoogle(userNameAttributeName, attributes);
    }
    
    ...
    
        private static OAuthAttributes ofNaver(String userNameAttributeName,
                                           Map<String, Object> attributes){
        Map<String, Object> response = (Map<String, Object>)attributes.get("response");
        
        return OAuthAttributes.builder()
                .name((String) response.get("name"))
                .email((String) response.get("email"))
                .picture((String) response.get("profile_image"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }
    
    ...

if 문으로 naver 판단 코드를 추가하고 ofNaver 라는 Naver 생성자를 만들어준 거다.

  1. index.mustache 에 네이버 로그인 버튼을 만들어주자.

  ...

  <a href="/oauth2/authorization/naver" class="btn btn-secondary active" role="button">Naver Login</a>

  ...

/oauth2/authorization/naver : 네이버 로그인 URl 은 application-oauth.properties 에 등록한 redirect-uri 값에 맞춰 자동으로 등록됩니다. 마지막 Path 만 각 소셜 로그인 코드를 사용하면 됩니다.

오...

테스트

  1. 로그인 버튼이 생겼다.
  1. 로그인 하고 글 등록을 누르면

...?

ㅎ.. 일단

  1. 네이버를 누르니 이렇게 들어왔다.

  2. h2-console 을 통해 user 로 role 변경하고 다시 로그아웃, 로그인 하면 글 써진다.

그런데

서버를 껐다켜도 NAVER GOOGLE 선택은 안나오네.
저게 왜 나왔을까?

한 번 더 보고 싶은딩 ㅠ

기존 테스트에 시큐리티 적용하기?

성공하고 글쓰는 것도 다 했는데 뭐를 또 하실까

마지막으로 기존 테스트에 시큐리티 적용으로 문제가 되는 부분들을 해결해보겠습니다.

그런게 있나요..?
잘 되는 거 같은데요..

문제가 되는 부분들은 대표적으로 다음고 같은 이유 때문입니다. 기존에는 바로 api 를 호출 할 수 있어 테스트 코드 역시 바로 api를 호출하도록 구성했는데, 시큐리티 옵션이 활성화되면 인증된 사용자만 api를 호출할 수 있습니다. 그래서 기존의 api 테스트 코드들은 모두 인증에 대한 권한을 받지 않았으므로 테스트 코드 마다 인증한 사용자가 호출한 것 처럼 작동하도록 수정해보겠습니다.

오... 앞서 우리는 test 코드를 이용해 test를 해오다가 스프링 시큐리티를 적용하면서부터 실제로 실행해보면서 테스트를 해봤지.
그럼 이제 test코드로도 이런 기능을 테스트해볼 수 있다는 건가!!

!!!
좋습니다 ㄱㄱ

test 코드로 테스트하기!

  1. 우측상단 gradle 클릭

  2. Tasks - verification - test 클릭으로 전체 테스트를 실행한다.

❌ ERROR

테스트 이벤트가 수진되지 않았습니다..?

org.junit.platform.launcher.core.DefaultLauncher handleThrowable
경고: TestEngine with ID 'junit-jupiter' failed to discover tests

왜지.
책은 롬복 외에 다른 테스트가 다 실패한다는데 나는 저렇게 딸랑 하나 나오네...ㅠ

어쨋든

그 이유를 하나씩 확인해보겠습니다.

좋습니다.

ERROR 고치기

문제 1. CustomOAuth2UserService 를 찾을 수 없음

첫번째 실패 테스트인 "hello가_리턴된다" 의 메시지를 보면 "No qualifying bean of type 'com. ... config.auth.CustomOAuth2UserService'" 라는 메시지가 등장합니다.

저는 안 뜨는데요 그런거...ㅠ
일단 따라가보겠습니다.
아니야 그럴 순 없어

테스트 메서드 하나하나 실행해보니 저렇게 나온다.

이는 CustomOAuth2UserService 를 생성하는데 필요한 소셜 로그인 관련 설정값들이 없기 때문에 발생합니다. 그렇다면! 이상합니다. 분명 application-oauth.properties 에 설정값들을 추가했는데 왜 설정이 없다고 할까요?
그 이유는 src/main 환경과 src/test 환경의 차이 때문입니다. 자동으로 설정을 가져오는 범위가 application.properties 까지입니다. 즉 application-oauth.properties 는 가져오지 않습니다.
이 문제를 해결하기 위해 테스트환경을 위한 application.properties 를 만들겠습니다.(실제 구글연동을 진행하는 건 아니므로 가짜 설정값을 등록해봅시다)

오오... 너무 어렵습니다. 선생님.

  1. src/test/resources 에 application.properties 를 만들어 코드를 넣는다.
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.session.store-type=jdbc

# Test OAuth

spring.security.oauth2.client.registration.google.client-id=test
spring.security.oauth2.client.registration.google.client-secret=test
spring.security.oauth2.client.registration.google.scope=profile,email
  1. test 안에 있는 IndexControllerTest 에 있는 메인페이지 로딩 메서드를 실행해본다.
    안되는데요 선생님 ;

  2. test 안에 domain/posts 에 있는 PostsRepositoryTest 안에 게시글저장_불러오기 메서드를 실행해본다이건 또 되네요 선생님..

  1. 같은 파일의 BaseTimeEntity_등록 메서드도 실행해본다.
    나는 잘 된다.

  2. posts_등록, 수정은 안된다.

문제2. 302 Status Code

Posts_등록된다 의 테스트 로그를 확인해봅시다.

응답의 결과로 200을 원했는데 302가 와서 실패했다는 겁니다. 이는 스프링 시큐리티 설정 때문에 인증되지 않은 사용자의 요청은 이동시키기 때문입니다. 그래서 이런 API 요청은 임의로 인증된 사용자를 추가해 API만 테스트해볼 수 있도록 만들어보겠습니다. 어려운 건아니고 이미 스프링 시큐리티에서 공식적으로 방법을 지원하고 있으므로 바로 사용해보겠습니다.

  1. 스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 spring-security-test 를 build.gradle 에 추가합니다.
	...

    testImplementation("org.springframework.security:spring-security-test")

	...
  1. PostsApiControllerTest 에 어노테이션을 추가한다.

@WithMockUser(roles="USER") : 인증된 모의 사용자를 만들고, roles 에 권한을 추가할 수 있습니다. 여기서는 USER 권한을 가진 사용자가 API를 요청하는 것같은 효과를 가집니다.

이정도만 하면 테스트가 성공할 거 같지만, 실제로 작동하진 않습니다. @WithMockUser 가 MockMvc 에서만 작동하기 때문입니다. 현재 PostsApiControllerTest 는 @SpringBootTest 로만 되어있으니 MockMvc 는 전혀 사용하지 않았습니다. 그래서 @SpringBootTest 에서 MockMvc 를 사용하는 방법을 소개합니다

빠밤.

SpringBootTest 에서 MockMvc 사용하기

  1. PostsApiControllerTest 코드를 변경한다.
    선생님의 깃허브 를 참고하자

손으로 쓰려면, springSecurity 와 post put 의 import 만 잘 하면 된다.

  1. 테스트해보기

나는 전체 테스트가 왜 안될까...ㅠ 하나하나 돌리면 되긴 되는데 쩝

Posts 테스트도 정상적으로 통과했으니 남은 테스트를 정리해보겠습니다.

예..

저는 전체 테스트도 안 되고,
이리 저리 만지다보니 이젠 하나하나 테스트 하는 것도 아예 안 되네여...
저는 틀렸나봅니다.. 먼저 가세요..

문제 3. @WebMvcTest 에서 CustomOAuth2UserService 를 찾을 수 없음

언제 삭제될지 모르니 사용하지 않으시는 걸 추천합니다.

????

넘어가자..

테스트는 구글링을 통해 찾은 그 어떤 방법으로도 작동하지 않았다.

  1. 설정에서 테스트 구동을 intellij 로 하기 ❌
    → 나는 처음부터 인텔리였다.

  2. 왼쪽 프로젝트 트리에서 test 를 오른쪽 클릭해 테스트 실행 ❌
    → 똑같다.

  3. gralde 버전 5로 맞추라는 에러메세지 ❌
    → 앞서 우리는 오류 때문에 gradle 버전을 4.10.2 으로 맞췄다. 그래서 test 만 5.8로 의존성에 설정해놓았다.
    → 전체 gradle 버전을 바꿔보고 싶지만, 그러면 앞서 생긴 오류가 다시 생길 수 있으니 하지 않도록 하자.

  4. 그레이들 리포트를 확인해보자❌
    → 테스트 실행조차 되지 않아 리포트에 아무것도 나오지 않는다.

  1. test { useJUnitPlatform() } 를 추가해주자. ❌
    → 원래 추가되어 있었다.

진짜 짜증난다.
다 잘 되는데 왜 테스트만 안 되는거야.
분명 그레이들 문제 같은데... 어디서부터 뭐를 건드려야할 지 감이 안온다.
dependency 를 싹 다 바꿔봤지만. 안 된다.

열받으니까 테스트는 넘어가고, 메인에서 수정할 것들만 수정해보자.

main 에 있는 내용만 수정해보자.

테스트는 넘어가고 테스트를 해결했을 미래의 나를 위해 main 코드만 수정해놓는다.

  1. Application.java 에서 @EnbaleJpaAuditing 을 제거합니다.

  2. config 패키지에 JpaConfig 를 생성하고 @EnbaleJpaAuditing 를 만든다.
    어노테이션만 넣으면된다.

// JpaConfig.java

package com.prac.webservice.springboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing  // JPA Auditing 활성화
public class JpaConfig {
}

모든 테스트를 통과했습니다!

...😟
그렇다치고 넘어갑시다..

앞의 과정을 토대로 스프링 시큐리티 적용으로 깨진 테스트를 적절하게 수정할 수 있게 되었습니다. 우리는 앞서 인텔리제이로 스프링 부트 통합 개발환경을 만들고 테스트와 JPA로 데이터를 처리하고 머스테치로 화면을 구성했으며 시큐리티와 Oauth로 인증과 권한을 배워보며 간단한 게시판을 모두 완성했습니다.

저같은 댕청이가 정말 장족의 발전입니다. 따라쓰기 밖에 한 게 없지만 영어단어들이 눈에 익은 것만으로도 감동적입니다. 감사합니다.

예전만 하더라도 스프링 시큐리티를 사용하기가 쉽지 않았습ㄴ디ㅏ. 하지만 계속 버전이 상향되어 최근 버전에서는 확장하기가 쉬워졌습니다. 꼭 필자의 선택인 스프링 부트 시큐리티 2.0 이 아니라 1.5을 사용해도 되지만, 언젠가는 업데이트를 해야만 합니다.

이제 AWS 를 이용해 나만의 서비스를 직접 배포하고 운영하느 과정을 진행하겠습니다.

아 좋습니다...

일단 다 뛰어넘고 AWS 로 가시지요. 테스트만 빼면 모두 잘 작동 하니까요.

profile
BEAT A SHOTGUN

0개의 댓글