각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 3 회고 (156일차)

제이온 (Jayon) 2021. 7. 7.

안녕하세요? 제이온입니다.

 

오늘은 잠실역 할리스에서 백엔드 팀원인 제리, 우기, 아론과 만나서 협업을 진행했습니다.

 

 

테이블 구조 설계 및 매핑

오늘 가장 많이 시간을 투자한 부분은 테이블 구조 설계와 매핑입니다. 이전에 아론이 DB 초기 구조를 잘 잡아 주었고, 거기서 User 테이블에 상속 관계가 생긴 것 외에는 달라진 것이 없었습니다.

 

 

 

 

여기서 유저는 소셜 로그인 유저와 비로그인 유저로 나뉘는데, 소셜 로그인 유저와 비로그인 유저가 갖는 공통적인 컬럼에 대해서만 User 테이블로 빼고, 나머지 각각의 성질에 맞게 소셜 로그인 유저 테이블과 비로그인 유저 테이블로 나누었습니다.

 

 

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
@RequiredArgsConstructor
@NoArgsConstructor
@Getter
public abstract class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    @Column(nullable = false)
    private String nickName;

}

@NoArgsConstructor
@Entity
@Getter
public class SocialLoginUser extends User {

    private String oauthId;

    @Enumerated(EnumType.STRING)
    private OAuthPlatform oauthPlatform;

    private String email;

    public SocialLoginUser(String nickName, String oauthId,
        OAuthPlatform oauthPlatform, String email) {
        super(nickName);
        this.oauthId = oauthId;
        this.oauthPlatform = oauthPlatform;
        this.email = email;
    }
    
}

@NoArgsConstructor
@Entity
public class GuestUser extends User {

    private String password;

    private GuestUser(String nickName, String password) {
        super(nickName);
        this.password = password;
    }

}

 

 

이것은 Inheritance 어노테이션과 DiscriminatorColumn 어노테이션 덕분에 구현하기 수훨했습니다. 여기서 Inheritance 어노테이션의 Join 방식은 no, name, price 속성을 갖는 Item이라는 테이블을 생성하여 Movie , Music , Book 테이블이 Item의 PK를 외래키로 갖는 방식이라고 합니다. 그리고 DiscriminatorColumn은 어떤 자식인지 구분하는 역할을 합니다. 만약, DB에 소셜 로그인 유저를 저장하였다면, 유저 테이블에서 Discriminator 컬럼은 SocialLoginUser가 되는 것입니다. 이러한 상속 관계를 통해 공통적인 속성은 분리할 수 있었습니다.

 

 

이 외의 유저와 댓글은 일대다 관계, 댓글과 프로젝트는 다대일 관계 등 관계를 설정해 줄 일이 있었는데, 이것은 ManyToOne과 같은 어노테이션을 통해 구현할 수 있었습니다. 아직 연관관계의 주인이라는 개념이 낯설어서, 다대일에서 '다'에 해당하는 테이블이 주로 연관관계의 주인 즉, 외래키 관리자 성격을 띄는 경우가 많다는 이야기를 들었습니다. 또한, 단방향 관계를 맺어줄 때는 JoinColumn 어노테이션을, 양방향 관계를 맺어줄 때는 MappedBy 속성을 준다는데 이 부분도 추후 자세히 학습해 봐야겠습니다.

 

 

카카오 소셜 로그인 기능 구현

이후에는 카카오 소셜 로그인 기능을 구현하였습니다. 기본적으로 리다이렉션보다는 팝업창을 통한 로그인 방식을 채택하였고, 우리가 구현한 oauth의 흐름은 아래와 같았습니다.

 

 

1. 사용자는 브라우저에서 카카오 로그인 요청을 한다.

 

2. 아이디, 비밀번호 정보가 일치하면 카카오 인증 서버는 액세스 토큰을 프론트엔드 서버(리액트)로 보내준다.

 

3. 프론트엔드 서버는 다시 액세스 토큰을 백엔드(스프링)으로 전송한다.

 

4. 스프링은 전달 받은 액세스 토큰을 카카오 API 서버에 전달해서 사용자 개인 정보(닉네임, 이메일 등)을 받아 오고 그 정보들을 DB에 저장한다.

 

5. 카카오에서 받은 액세스 토큰은 더이상 사용하지 않고, 서버에서 JWT 토큰을 새로 발급하여 프론트엔드 서버로 전송한다.

 

6. 프론트엔드 서버는 JWT 토큰을 브라우저의 localStorage나 쿠키 혹은 다른 어떠한 방식으로 저장해 놓고, api 통신할 때마다 필요하다면 헤더로 JWT 토큰을 담아서 백엔드 서버로 전송한다.

 

 

여기서 백엔드는 4번과 5번 과정을 진행하였습니다. 왜냐하면 리액트 단에서 카카오의 액세스 토큰을 발급하여 스프링 단으로 보내줄 것이기 때문이죠. 그리고 해당 파트는 우기가 미리 주말에 구현을 했어서 쉽게 기능을 뚝딱 만들 수 있었습니다.

 

 

    public String login(String accessToken) {
        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders apiRequestHeader = new HttpHeaders();
        apiRequestHeader.add("Authorization", "Bearer " + accessToken);
        apiRequestHeader.add("Content-type", "application/x-www-form-urlencoded;charset=utf8");
        HttpEntity<HttpHeaders> apiRequest = new HttpEntity<>(apiRequestHeader);

        HttpEntity<String> apiResponse = restTemplate.exchange(
            "https://kapi.kakao.com/v2/user/me",
            HttpMethod.POST,
            apiRequest,
            String.class
        );

        JSONObject jsonObject = new JSONObject(apiResponse.getBody());
        Long oauth_id = jsonObject.getLong("id");
        JSONObject kakao_account = (JSONObject) jsonObject.get("kakao_account");
        String email = kakao_account.getString("email");
        JSONObject profile = (JSONObject) kakao_account.get("profile");
        String nickname = profile.getString("nickname");

        User socialLoginUser = new SocialLoginUser(nickname, oauth_id.toString(),
            OAuthPlatform.KAKAO, email);

        userRepository.save(socialLoginUser);

        // 토큰 발급
        return tokenProvider.createToken(socialLoginUser.getId().toString());
    }

 

 

전반적인 리팩토링은 내일 할 예정이고, 일단 기능만 돌아가게 만들었습니다. 파라미터로 받은 액세스 토큰을 헤더에 담아서 카카오 API 서버로 요청을 합니다. 이때 RestTemplate 라이브러리를 사용하였고, 받은 응답값을 JSONObject를 사용하여 원하는 데이터만 추출을 하였습니다. 결과적으로 SocialLoginUser 객체를 만들어서 우리의 DB에 저장하는 데 까지 성공하였고, 이후에 자체적인 JWT 토큰 생성 객체를 만들어서 토큰까지 생성해 주었습니다.

 

이제, 내일은 해당 코드를 이쁘게 리팩토링하고 댓글 기능 및 토큰 인증 기능을 추가할 예정입니다.

 

 

정리

오늘 생각한 주요 기능을 모두 구현할 수 있어서 굉장히 뿌듯한 하루였습니다. 다만, 저녁에 S사에서의 최종 면접 탈락이라는 결과가 나왔고 꽤나 상심을 했습니다. 그래도 아직 기회는 있기에 실패를 발판 삼아서 더욱 열심히 해 보려고 합니다.

댓글

추천 글