Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 회원가입/로그인 후 토큰을 반환한다. #212

Merged
merged 18 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ea8e4f2
feat: 카카오 서버에 토큰 요청하는 기능 구현
JINU-CHANG Aug 1, 2024
4ea13ff
feat: RestClient ExceptionHandler 등록 및 RestConfig 파일 추가
JINU-CHANG Aug 1, 2024
4193e32
feat: 사용자 정보 가져오는 기능 구현
JINU-CHANG Aug 1, 2024
7cb17b4
Merge remote-tracking branch 'origin/dev-be' into feat/181-kakao-login
JINU-CHANG Aug 1, 2024
802341c
Merge remote-tracking branch 'origin/dev-be' into feat/181-kakao-login
JINU-CHANG Aug 1, 2024
88c50d9
fix: 머지 후 충돌 해결
JINU-CHANG Aug 1, 2024
8bbd84b
style: RestClient -> OauthClient로 변경
JINU-CHANG Aug 2, 2024
a0f2731
feat: User 스키마에 email 필드 추가
JINU-CHANG Aug 2, 2024
4dad808
chore: jwt 라이브러리 추가
JINU-CHANG Aug 2, 2024
5d97202
feat: 회원가입 및 로그인 기능 구현
JINU-CHANG Aug 3, 2024
c1120da
feat: 토큰 반환하는 컨트롤러 로직 구현 및 Mock 테스트 추가
JINU-CHANG Aug 3, 2024
507cb53
Merge remote-tracking branch 'origin/dev-be' into feat/181-kakao-login
JINU-CHANG Aug 3, 2024
76426ba
refactor: 불필요한 어노테이션 삭제 및 텍스트 픽스처 생성
JINU-CHANG Aug 3, 2024
32c5fdf
refactor: User deleted 필드 삭제
JINU-CHANG Aug 3, 2024
ebec67c
feat: 토큰 확인하는 메서드 생성
JINU-CHANG Aug 3, 2024
0392c9e
refactor: jwt 토큰 정보에 id값만 담도록 수정
JINU-CHANG Aug 4, 2024
5ac5895
refactor: mock 테스트의 sql어노테이션 삭제
JINU-CHANG Aug 5, 2024
3992b57
style: static 변수 네이밍 변경
JINU-CHANG Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/bang-ggood/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-gson:0.11.2'
runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public CategoryService(CategoryPriorityRepository categoryPriorityRepository) {

@Transactional
public void createCategoriesPriority(CategoryPriorityCreateRequest request) {
User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
shin-jisong marked this conversation as resolved.
Show resolved Hide resolved
validate(request);
List<CategoryPriority> categoryPriorities = request.categoryIds().stream()
.map(id -> new CategoryPriority(id, user))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
import com.bang_ggood.checklist.service.ChecklistService;
import com.bang_ggood.user.domain.User;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.net.URI;
import java.util.List;


@RestController
Expand Down Expand Up @@ -48,7 +48,7 @@ public ResponseEntity<SelectedChecklistResponse> readChecklistById(@PathVariable

@GetMapping("/checklists")
public ResponseEntity<UserChecklistsPreviewResponse> readUserChecklistsPreview() {
User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
return ResponseEntity.ok(checklistService.readUserChecklistsPreview(user));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public long createChecklist(ChecklistCreateRequest checklistCreateRequest) {
Room room = roomRepository.save(checklistCreateRequest.toRoomEntity());

ChecklistInfo checklistInfo = checklistCreateRequest.toChecklistInfo();
Checklist checklist = new Checklist(new User(1L, "방방이"), room, checklistInfo.deposit(), checklistInfo.rent(),
Checklist checklist = new Checklist(new User(1L, "방방이", "[email protected]"), room, checklistInfo.deposit(), checklistInfo.rent(),
checklistInfo.contractTerm(), checklistInfo.realEstate());
checklistRepository.save(checklist);

Expand Down Expand Up @@ -126,7 +126,7 @@ private void createChecklistQuestions(ChecklistCreateRequest checklistCreateRequ

@Transactional
public ChecklistQuestionsResponse readChecklistQuestions() {
User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
List<CustomChecklistQuestion> customChecklistQuestions = customChecklistQuestionRepository.findByUser(user);

Map<Category, List<Question>> categoryQuestions = customChecklistQuestions.stream()
Expand Down Expand Up @@ -233,7 +233,7 @@ private List<BadgeResponse> createBadges(List<ChecklistQuestion> questions) {

@Transactional
public ChecklistsWithScoreReadResponse readChecklistsComparison(List<Long> checklistIds) {
User user = new User(1L, "방끗");
User user = new User(1L, "방끗", "[email protected]");

validateChecklistComparison(checklistIds);

Expand Down Expand Up @@ -290,7 +290,7 @@ public void updateCustomChecklist(CustomChecklistUpdateRequest request) {
validateCustomChecklistQuestionsIsNotEmpty(questionIds);
validateCustomChecklistQuestionsDuplication(questionIds);

User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
customChecklistQuestionRepository.deleteAllByUser(user);

List<CustomChecklistQuestion> customChecklistQuestions = questionIds.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,34 @@

import com.bang_ggood.user.dto.request.OauthLoginRequest;
import com.bang_ggood.user.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

public static final String TOKEN_COOKIE_NAME = "token";
shin-jisong marked this conversation as resolved.
Show resolved Hide resolved
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@PostMapping("/oauth/login")
public void login(OauthLoginRequest request) {
userService.login(request);
public ResponseEntity<Void> login(@Valid @RequestBody OauthLoginRequest request) {
String token = userService.login(request);

ResponseCookie cookie = ResponseCookie
.from(TOKEN_COOKIE_NAME, token)
.httpOnly(true)
.path("/")
.build();

return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ public class User extends BaseEntity {
@Column(nullable = false)
private String name;

public User(Long id, String name) {
this.id = id;
@Column(nullable = false)
private String email;

public User(String name, String email) {
this.name = name;
this.email = email;
}

public User(String name) {
public User(Long id, String name, String email) { // TODO 테스트용
this.id = id;
this.name = name;
this.email = email;
}

protected User() {
Expand All @@ -36,6 +41,14 @@ public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -58,6 +71,7 @@ public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.bang_ggood.user.dto.request;

public record OauthLoginRequest(String code) {
import jakarta.validation.constraints.NotBlank;

public record OauthLoginRequest(@NotBlank(message = "인가 코드가 존재하지 않습니다.") String code) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.bang_ggood.user.dto.response;

import com.bang_ggood.user.domain.User;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record OauthInfoResponse(String id, String connected_at, KakaoAccountResponse kakao_account) {

public User toUserEntity() {
return new User(kakao_account.name(), kakao_account.email());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import com.bang_ggood.exception.ExceptionCode;
import com.bang_ggood.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

default User getUserById(Long id) {
return findById(id).orElseThrow(() -> new BangggoodException(ExceptionCode.USER_NOT_FOUND));
}

Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bang_ggood.user.service;

import jakarta.validation.constraints.NotNull;

public record AuthUser(@NotNull Long id) {

public static AuthUser from(Long id) {
return new AuthUser(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.bang_ggood.user.service;

import com.bang_ggood.user.domain.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtTokenProvider {

private final String secretKey;
private final int tokenExpirationMills;

public JwtTokenProvider(
@Value("${jwt.secret-key}") String secretKey,
@Value("${jwt.expiration-millis}") int tokenExpirationMills) {
this.secretKey = secretKey;
this.tokenExpirationMills = tokenExpirationMills;
}

public String createToken(User user) {
Date now = new Date();
Date expiredDate = new Date(now.getTime() + tokenExpirationMills);

return Jwts.builder()
.setSubject(user.getId().toString())
.setIssuedAt(now)
.setExpiration(expiredDate)
.signWith(Keys.hmacShaKeyFor(secretKey.getBytes()))
.compact();
}

public AuthUser resolveToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();

Long id = Long.valueOf(claims.getSubject());
return AuthUser.from(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package com.bang_ggood.user.service;

import com.bang_ggood.user.domain.User;
import com.bang_ggood.user.dto.request.OauthLoginRequest;
import com.bang_ggood.user.dto.response.OauthInfoResponse;
import com.bang_ggood.user.repository.UserRepository;
import org.springframework.stereotype.Service;

@Service
public class UserService {

private final OauthClient oauthClient;
private final JwtTokenProvider jwtTokenProvider;
private final UserRepository userRepository;

public UserService(OauthClient oauthClient) {
public UserService(OauthClient oauthClient, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) {
this.oauthClient = oauthClient;
this.jwtTokenProvider = jwtTokenProvider;
this.userRepository = userRepository;
}

public void login(OauthLoginRequest request) {
oauthClient.requestOauthInfo(request);
public String login(OauthLoginRequest request) {
OauthInfoResponse oauthInfoResponse = oauthClient.requestOauthInfo(request);

User user = userRepository.findByEmail(oauthInfoResponse.kakao_account().email())
.orElseGet(() -> userRepository.save(oauthInfoResponse.toUserEntity()));

return jwtTokenProvider.createToken(user);
}
}
4 changes: 2 additions & 2 deletions backend/bang-ggood/src/main/resources/data.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
INSERT INTO users(id, name, created_at, modified_at, deleted)
VALUES (1, '방방이', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false);
INSERT INTO users(id, name, email, created_at, modified_at, deleted)
VALUES (1, '방방이', '[email protected]', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false);

INSERT INTO custom_checklist_question(user_id, question, created_at, modified_at, deleted)
VALUES (1, 'CLEAN_1', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false),
Expand Down
1 change: 1 addition & 0 deletions backend/bang-ggood/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP(6),
modified_at TIMESTAMP(6),
deleted BOOLEAN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.bang_ggood;

import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.web.servlet.MockMvc;

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AcceptanceMockTestSupport {

@Autowired
protected MockMvc mockMvc;

@LocalServerPort
protected int port;

@BeforeEach
void setPort() {
RestAssured.port = port;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public class ChecklistFixture {

public static final Checklist checklist = new Checklist(
new User(1L, "방방이"),
new User(1L, "방방이", "[email protected]"),
shin-jisong marked this conversation as resolved.
Show resolved Hide resolved
RoomFixture.ROOM_1,
1000, 50, 12, "방끗공인중개사"
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import com.bang_ggood.category.domain.Category;
import com.bang_ggood.checklist.ChecklistFixture;
import com.bang_ggood.checklist.domain.Checklist;
import com.bang_ggood.checklist.dto.request.CustomChecklistUpdateRequest;
import com.bang_ggood.checklist.dto.request.ChecklistCreateRequest;
import com.bang_ggood.checklist.dto.request.CustomChecklistUpdateRequest;
import com.bang_ggood.checklist.dto.response.ChecklistQuestionsResponse;
import com.bang_ggood.checklist.dto.response.ChecklistsWithScoreReadResponse;
import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse;
Expand All @@ -18,10 +18,10 @@
import com.bang_ggood.room.domain.Room;
import com.bang_ggood.room.repository.RoomRepository;
import com.bang_ggood.user.domain.User;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

import static com.bang_ggood.checklist.CustomChecklistFixture.CUSTOM_CHECKLIST_UPDATE_REQUEST;
import static com.bang_ggood.checklist.CustomChecklistFixture.CUSTOM_CHECKLIST_UPDATE_REQUEST_DUPLICATED;
Expand Down Expand Up @@ -206,7 +206,7 @@ void readChecklistById_invalidChecklistId_exception() {
@Test
void readChecklistsComparison() {
// given
User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
shin-jisong marked this conversation as resolved.
Show resolved Hide resolved
Room room1 = RoomFixture.ROOM_1;
Room room2 = RoomFixture.ROOM_2;
Room room3 = RoomFixture.ROOM_3;
Expand Down Expand Up @@ -242,7 +242,7 @@ void readChecklistsComparison_invalidIdCount() {
@Test
void readChecklistsComparison_invalidId() {
// given
User user = new User(1L, "방방이");
User user = new User(1L, "방방이", "[email protected]");
Room room1 = RoomFixture.ROOM_1;
Room room2 = RoomFixture.ROOM_2;
Room room3 = RoomFixture.ROOM_3;
Expand Down Expand Up @@ -271,7 +271,7 @@ void updateCustomChecklist() {
checklistService.updateCustomChecklist(request);

// then
assertThat(customChecklistQuestionRepository.findByUser(new User(1L, "방방이")))
assertThat(customChecklistQuestionRepository.findByUser(new User(1L, "방방이", "[email protected]")))
.hasSize(request.questionIds().size());
}

Expand Down
Loading
Loading