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

🚀 2단계 - 깃헙 로그인 구현 #613

Open
wants to merge 4 commits into
base: beyondorder
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# 지하철 노선도 미션
[ATDD 강의](https://edu.nextstep.camp/c/R89PYi5H) 실습을 위한 지하철 노선도 애플리케이션
[ATDD 강의](https://edu.nextstep.camp/c/R89PYi5H) 실습을 위한 지하철 노선도 애플리케이션


### 기능 요구사항
* 깃허브를 이용한 로그인 구현(토큰 발행)
* 가입이 되어있지 않은 경우 회원 가입으로 진행 후 토큰 발행


### 프로그래밍 요구사항
* GitHub 로그인을 검증할 수 있는 인수 테스트 구현(실제 GitHub에 요청을 하지 않아도 됨)
44 changes: 44 additions & 0 deletions src/main/java/nextstep/auth/application/GithubClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package nextstep.auth.application;

import nextstep.fake.GithubAccessTokenRequest;
import nextstep.fake.GithubAccessTokenResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.List;


@Component
public class GithubClient {

@Value("${github.url}")
private String githubUrl;

public String requestGithubToken(String code) {
RestTemplate restTemplate = new RestTemplate();

GithubAccessTokenRequest request = new GithubAccessTokenRequest(
code,
"client_id",
"client_secret"
);

HttpHeaders headers = new HttpHeaders();
headers.setAccept(List.of(MediaType.APPLICATION_JSON));

HttpEntity httpEntity = new HttpEntity(request, headers);
String accessToken = restTemplate.exchange(githubUrl, HttpMethod.POST, httpEntity, GithubAccessTokenResponse.class)
.getBody()
.getAccessToken();

return accessToken;
}



}
19 changes: 19 additions & 0 deletions src/main/java/nextstep/auth/application/GithubLoginService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package nextstep.auth.application;

import nextstep.auth.application.dto.GitHubLoginRequest;
import nextstep.auth.application.dto.GithubLoginResponse;
import org.springframework.stereotype.Service;

@Service
public class GithubLoginService {

private final GithubClient githubClient;

public GithubLoginService(GithubClient githubClient) {
this.githubClient = githubClient;
}

public GithubLoginResponse login(GitHubLoginRequest request) {
return new GithubLoginResponse(githubClient.requestGithubToken(request.getCode()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nextstep.auth.application.dto;

public class GitHubLoginRequest {
private String code;

public GitHubLoginRequest() {
}

public GitHubLoginRequest(String code) {
this.code = code;
}

public String getCode() {
return code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nextstep.auth.application.dto;

public class GithubLoginResponse {
private final String accessToken;

public GithubLoginResponse(String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}
}
26 changes: 26 additions & 0 deletions src/main/java/nextstep/auth/ui/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.auth.ui;

import nextstep.auth.application.GithubLoginService;
import nextstep.auth.application.dto.GitHubLoginRequest;
import nextstep.auth.application.dto.GithubLoginResponse;
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 AuthController {

private final GithubLoginService githubLoginService;

public AuthController(GithubLoginService githubLoginService) {
this.githubLoginService = githubLoginService;
}

@PostMapping("/login/github")
public ResponseEntity<GithubLoginResponse> login(
@RequestBody GitHubLoginRequest request
) {
return ResponseEntity.ok(githubLoginService.login(request));
}
}
3 changes: 2 additions & 1 deletion src/main/java/nextstep/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nextstep.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand All @@ -9,7 +10,7 @@ public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse(e.getMessage()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰반영 💯

}

}
25 changes: 25 additions & 0 deletions src/main/java/nextstep/fake/GithubAccessTokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package nextstep.fake;

public class GithubAccessTokenRequest {
private final String code;
private final String clientId;
private final String clientSecret;

public GithubAccessTokenRequest(String code, String clientId, String clientSecret) {
this.code = code;
this.clientId = clientId;
this.clientSecret = clientSecret;
}

public String getCode() {
return code;
}

public String getClientId() {
return clientId;
}

public String getClientSecret() {
return clientSecret;
}
}
18 changes: 18 additions & 0 deletions src/main/java/nextstep/fake/GithubAccessTokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nextstep.fake;

public class GithubAccessTokenResponse {

private String accessToken;

public GithubAccessTokenResponse() {
}

public GithubAccessTokenResponse(String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}

}
38 changes: 38 additions & 0 deletions src/main/java/nextstep/fake/GithubResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.fake;

public enum GithubResponse {
사용자1("code1", "accessToken1","[email protected]"),
사용자2("code2", "accessToken2","[email protected]"),
사용자3("code3", "accessToken3","[email protected]");
Comment on lines +3 to +6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fake 객체가 main에 있네요. 😅


private final String code;
private final String accessToken;
private final String email;

GithubResponse(String code, String accessToken, String email) {
this.code = code;
this.accessToken = accessToken;
this.email = email;
}

public static GithubResponse findByCode(String code) {
for (GithubResponse githubResponse : GithubResponse.values()) {
if (githubResponse.code.equals(code)) {
return githubResponse;
}
}
throw new IllegalArgumentException("해당하는 코드가 없습니다.");
}

public String getCode() {
return code;
}

public String getAccessToken() {
return accessToken;
}

public String getEmail() {
return email;
}
}
17 changes: 17 additions & 0 deletions src/main/java/nextstep/fake/TestController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.fake;

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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

@PostMapping("/github/access-token")
public ResponseEntity<GithubAccessTokenResponse> test(@RequestBody GithubAccessTokenRequest request) {
return ResponseEntity.ok(new GithubAccessTokenResponse(GithubResponse.findByCode(request.getCode()).getAccessToken()));
}
Comment on lines +13 to +16

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인수테스트에서 fake(test/github/access-token)을 호출하는 부분이 보이지 않아요. 제가 못찾은 걸까요? 🤔

}
24 changes: 6 additions & 18 deletions src/main/java/nextstep/favorite/application/FavoriteService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import nextstep.subway.applicaion.dto.PathResponse;
import nextstep.subway.domain.Station;
import nextstep.subway.domain.StationRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -34,39 +35,26 @@ public FavoriteService(FavoriteRepository favoriteRepository, MemberRepository m
this.pathService = pathService;
}

/**
* TODO: LoginMember 를 추가로 받아서 FavoriteRequest 내용과 함께 Favorite 를 생성합니다.
* @param request, loginMember
*/
@Transactional
public void createFavorite(FavoriteRequest request, LoginMember loginMember) {
public Favorite createFavorite(FavoriteRequest request, LoginMember loginMember) {
Member member = getMember(loginMember);
Station source = this.stationRepository.findById(request.getSource()).orElseThrow(() -> new IllegalArgumentException("존재하지 않는 출발역입니다."));
Station target = this.stationRepository.findById(request.getTarget()).orElseThrow(() -> new IllegalArgumentException("존재하지 않는 도착역입니다."));
Station source = this.stationRepository.findById(request.getSource()).orElseThrow(() -> new RuntimeException("존재하지 않는 출발역입니다."));
Station target = this.stationRepository.findById(request.getTarget()).orElseThrow(() -> new RuntimeException("존재하지 않는 도착역입니다."));
Comment on lines +41 to +42

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception의 범위가 넓어졌네요. RuntimeException으로 바꾼 이유가 있을까요? 🤔


PathResponse path = this.pathService.findPath(source.getId(), target.getId());
if(path.hasNoPath()){
throw new IllegalArgumentException("경로가 존재하지 않습니다.");
}
favoriteRepository.save(new Favorite(member, source, target));
return favoriteRepository.save(new Favorite(member, source, target));
}

/**
* TODO: StationResponse 를 응답하는 FavoriteResponse 로 변환해야 합니다.
*
* @return
*/
public List<FavoriteResponse> findFavorites(LoginMember loginMember) {
Member member = getMember(loginMember);
List<Favorite> favorites = favoriteRepository.findAllByMemberId(member.getId());
return favorites.stream().map(FavoriteResponse::of).collect(Collectors.toList());
}

/**
* TODO: 요구사항 설명에 맞게 수정합니다.
* @param id
*/

@Transactional
public void deleteFavorite(Long id, LoginMember loginMember) {
Member member = getMember(loginMember);
Favorite favorite = favoriteRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("존재하지 않는 즐겨찾기입니다."));
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/nextstep/favorite/ui/FavoriteController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import nextstep.favorite.application.FavoriteService;
import nextstep.favorite.application.dto.FavoriteRequest;
import nextstep.favorite.application.dto.FavoriteResponse;
import nextstep.favorite.domain.Favorite;
import nextstep.member.domain.LoginMember;
import nextstep.member.ui.AuthenticationPrincipal;
import org.springframework.http.ResponseEntity;
Expand All @@ -13,21 +14,20 @@

@RestController
public class FavoriteController {
private FavoriteService favoriteService;
private final FavoriteService favoriteService;

public FavoriteController(FavoriteService favoriteService) {
this.favoriteService = favoriteService;
}

@PostMapping("/favorites")
public ResponseEntity createFavorite(
public ResponseEntity<FavoriteResponse> createFavorite(
@RequestBody FavoriteRequest request,
@AuthenticationPrincipal LoginMember loginMember
) {
favoriteService.createFavorite(request, loginMember);
Favorite favorite = favoriteService.createFavorite(request, loginMember);
return ResponseEntity
.created(URI.create("/favorites/" + 1L))
.build();
.created(URI.create("/favorites/" + favorite.getId())).body(FavoriteResponse.of(favorite));
}

@GetMapping("/favorites")
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/nextstep/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ public void update(Member member) {
public boolean checkPassword(String password) {
return Objects.equals(this.password, password);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Member member = (Member) o;
return Objects.equals(id, member.id) && Objects.equals(email, member.email) && Objects.equals(password, member.password) && Objects.equals(age, member.age);
}

@Override
public int hashCode() {
return Objects.hash(id, email, password, age);
}
}
7 changes: 7 additions & 0 deletions src/main/resources/application-test.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

security.jwt.token.secret-key= atdd-secret-key
security.jwt.token.expire-length= 3600000

github.url = http://localhost:8080/test/github/access-token
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ spring.jpa.properties.hibernate.format_sql=true

security.jwt.token.secret-key= atdd-secret-key
security.jwt.token.expire-length= 3600000

github.url = https://api.github.com
Loading