real-world프로젝트 Spring 시작하기(7) - 회원가입API 및 Jwt Token 만들기(1)
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
- https://www.logicbig.com/tutorials/misc/jackson/json-type-info-with-wrapper.html JSON어노테이션
- https://seongtak-yoon.tistory.com/70 JSON어노테이션 구현체 분리
- https://wildeveloperetrain.tistory.com/49 Service Impl 분리이유