Post

real-world프로젝트 Spring 시작하기(7) - 회원가입API 및 Jwt Token 만들기(1)

real-world project 구현 과정 카테고리 보기

Spring Security를 이용한 JWT token 적용하기.

앞선 포스팅에서 JWT와 쿠키, 세션에 대해서 설명하였다.

이번 포스팅에서는 이거를 코드로 구현할 것이다.

회원가입API를 구현하면 Return값으로 Token을 넘겨야하기 때문이다.

API마다 토큰을 가지고 검증하기 위해 Filter를 이용하지만 지금 단계에서는 필요하지 않으므로 Token을 만드는것까지만 해볼 것이다.

✔ API명세 확인

real-world에서는 회원가입(Registration)에 대해 명세를 이렇게 주었다.

회원가입명세

  • Endpoint: /api/users [post]
  • body: user객체.
  • 인증은 필요 없다.
  • email, username, password 가 필요하다.

인증 및 인가는 필요없으니 필터를 아직 구현할 필요가 없는것이다.

✔ 회원가입 Request DTO 만들기

username, email, password를 필드로 가지는 user객체를 넘겨 받기로 했으니 이를 받아주는 객체가 필요할 것이다.

나는 UserSignupRequest라는 객체로 만들었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.io.realworld.domain.aggregate.user.dto;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@Getter
@Builder
@JsonTypeName("user")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
public class UserSignupRequest {

    @NotBlank
    private String username;
    @Email
    private String email;
    @NotBlank
    private String password;
}

DDD설계를 따르기 위해 패키지 구조를 저렇게 가져갔다는걸 확인하면 좋다.

위에서 부터 설명을 하자.

@Getter

1
2
3
4
5
6
7
Example:
      private @Getter int foo;
  
will generate:
      public int getFoo() {
          return this.foo;
      }

라고 doc에는 적혀있다. 롬복에서 제공한다.

필드레벨에 달면 저렇게 코드를 생성해주고 클래스 레벨에 @Getter어노테이션을 달면 클래스 내에 선언된 필드 전체에 적용이 된다는 것이다.

이렇게 @Getter를 쓴 이유는 저기에 쓰인 필드 모두 어딘선가 재사용될 가능성이 굉장히 크기 때문이다. username과 email로 중복 검증받아야할 일도 있고, password는 암호화할때 쓰일것이다.

그래서 클래스 레벨에 @Getter를 달아놓는것이다. 사실 @Setter만 아니면 클래스레벨에 쓰는건 크게 신경 쓰지 않아도 되는것 같다.

@Builder

Builder패턴은 많은 이점을 가지고 있다.

**Bulder를 사용한 이유는 ** Builder를 통해 추가적인 연산이 필요하지만 여러 생성자에 대해 유연하게 대처가 가능하고,코드를 간략히 짤 수가 있게 된다. 자세한건 따로 정리하고 여기에는 적지 않겠다.

@Builder는 생성자가 없을 경우 모든 멤버 변수를 파라미터로 가지는 기본 생성자를 생성하는데, 이 때문에 @AllArgsConstructor를 명시해주지 않아도 된다. 만약 @NoArgsConstructor@Builder와 같이 쓰는경우에는 명시해줘야한다.

@JsonTypeName, @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)

JSON의 논리적 이름을 명시해준다는 것이다.

real-world에서는 요청에 대한 JSON형식을 user라는 객체에 담아 보내기로 되어있다.

이를 받기 위해서 필요한게 이 어노테이션이고 이름을 맞춰줘야한다.

1
2
3
4
5
6
7
{
  "user": {
    "email": "test@naver.com",
    "password": "<password>",
    "username": "kms"
  }
}

하지만 단독으로 쓸 경우에는 위처럼 요청오는 JSON을 파싱할 수도, 응답을 보내기가 힘들다.

그래서 @JsonTypeInfo 이 구문이 필요한데, 첫번째 인자인 JsonTypeInfo.Id.NAME는 사용자가 @JsonTypeName으로 지정한 이름을 객체의 이름으로 지정하겠다는 뜻이다.

JsonTypeInfo.As.WRAPPER_OBJECT구문은 JSON전체에 대한 객체를 오브젝트 형식으로 묶는다는 뜻이다.

JsonTypeInfo.As.WRAPPER_Array도 존재하는데 이를 사용할 경우 Request는 다음과 같은 양식으로 받아야할 것이다.

1
2
3
4
5
6
7
[
  "user": {
    "email": "test@naver.com",
    "password": "<password>",
    "username": "kms"
  }
]

@NotBlank, @Email

javax.validation에 정의되어 있는 이 두 어노테이션은 검증을 담당한다.

  • @NotBlank: 하나 이상의 문자가 들어가야한다. (“”,” “)불가.
  • @Email: Java Bean에서 제공하고 있는 검증방식. 이메일 검증을 한다.

이렇게 Request로 들어오는 DTO에 대한 코드 설명은 끝이 났다.

이제 이를 받는 컨트롤러를 설계한다.

✔ 회원가입 컨트롤러 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/api/users")
public class UsersController {

    private final UserServiceImpl userService;

    public UsersController(UserServiceImpl userService) {
        this.userService = userService;
    }


    @PostMapping
    public UserResponse signup(@Valid @RequestBody  UserSignupRequest userSignupRequest) {
        return userService.signup(userSignupRequest);
    }
}

기본적인 코드이므로 따로 설명할 건 없다. 쓴 이유에 대해서는 알아야겠다.

  • @RestController: real-world에서는 모든 응답을 JSON방식으로 바디에 담아서 넘겨야한다. @ResponseBody@Controller역할을 둘 다 하는 @RestController를 채택
  • @RequestMapping: 공통 엔드포인트를 지정하기 위해 클래스단에다가 넣어두었다.

  • private final ~: @RequiredArgsConstructor어노테이션을 사용하면 생성자 주입을 따로 안해줘도 되지만 방식 자체를 까먹을까봐 명시해두었다.

  • @PostMapping: 회원가입은 Post형식으로 받을것이므로 해당 어노테이션을 사용하였다.

  • @Valid: DTO에서 사용한 검증 어노테이션에 대한 검증을 진행한다. 같은 자바빈즈 패키지에서 지원하기에 검증할 수 있다.

  • @RequestBody: 요청 본문에 담긴 값을 HttpMessageConvert가 우리가 선언한 자바객체형식으로 반환해서 꽂아넣어준다.

근데, HttpMessageConver는 키와 밸류를 기준으로 데이터를 꽂아주는데 real-world가 요청형식으로 보내는 것은 user라는 객체로 감싸져 있다. 이를 해독하기 위해서는 DTO클래스 어노테이션 @JsonTypeInfo@JsonTypeName이 필요한 것이다.

✔ 회원가입 Response DTO 만들기

위 코드를 보면 컨트롤러의 리턴 객체타입이 UserResponse이다.

real-world에서는 리턴객체를 다음과 같이 지정해주고 있다.

1
2
3
4
5
6
7
8
9
{
  "user": {
    "email": "jake@jake.jake",
    "token": "jwt.token.here",
    "username": "jake",
    "bio": "I work at statefarm",
    "image": null
  }
}

이 필드값에 맞는 DTO를 Request DTO를 만들었던거와 똑같이 생성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
@Builder
@Getter
@JsonTypeName("user")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
public class UserResponse {

    private String email;
    private String token;
    private String username;
    private String bio;
    private String image;
}

위의 내용과 같으므로 따로 설명하지는 않겠다.

여기까지가 회원가입을 하기 위한 컨트롤러 제작이다.

요청 DTO, 응답 DTO, 컨트롤러, 어노테이션에 대한 설명을 하였고,

다음 포스팅에서는 Token만들어서 리턴하기, 회원가입 서비스, 레포지토리 구현을 포스팅할 것이다.

현재 이 프로젝트는 vue.js + vite + vuex + Spring boot, Spring Data JPA + Spring Security 를 이용하여 real-world 데모버전을 제작완료하였습니다. https://github.com/kkminseok/real-world-springboot

Reference

This post is licensed under CC BY 4.0 by the author.