-
Notifications
You must be signed in to change notification settings - Fork 46
[BE][team9] oauth + jwt 를 이용한 토큰 인증방식 구현 #46
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
base: team9
Are you sure you want to change the base?
Conversation
다음 이닝으로 넘어갈 때, base에 서있던 선수들이 사라지지 않는 버그 수정
user가 게임에 참여중인지 또는 참여중이 아닌지 확인하는 로직을 User 객체 내부로 이동 시켰습니다.
game의 대기중, 게임중, 종료 세 가지 상태를 나타내는 status 칼럼 추가
2번 사용자가 원정팀으로 참가한 상태의 첫번째 게임방 생성하도록 초기화
1. 적절한 상황에 맞게 Game의 status(WAIT, PLAYING, EXITED) 를 바꿔줍니다. 2. Game에 참가하거나 진행시킬 수 있는지 체크하는 조건에 status 값을 추가했습니다.
게임방에서 나가는 api 구현
현재 상황이 사용자가 요청한 동작을 수행하기에 적절하지 못할 때 발생하는 BadStatusException을 만들었습니다.
1. goToNextInning 메소드에서 야구게임이 종료된 상황이면 status를 EXITED로만 변경하도록 했습니다.
2. 종료조건: 9회말에서 두 팀의 점수가 같지 않을 경우 (같으면 12회까지 연장)
12회말까지 가면 그냥 종료
jwt 사용을 위한 jjwt 라이브러리 의존성 추가
jwt를 발급하고, jwt의 정보를 읽어주는 JwtTokenUtil 생성
GlobalExceptionHandler에서 jwt 관련 Exception 처리하도록 추가
createToken 메소드에 expiredMinute를 설정하여 토큰 유효시간을 설정할 수 있도록 하였습니다.
1. authorization으로 access token을 받아오는 메소드 구현 2. access token으로 github email을 가져오는 메소드 구현 3. OauthException을 만들어 oauth 과정에서 발생하는 Exception 처리
user와 1:n 관계로 설계해놓은 oauth_access_token 테이블이 필요없어서 제거하였습니다.
1. oauth 를 통해 email을 가져오고나면 signIn 메소드를 통해 로그인된 user에 대한 jwt를 발급합니다. 2. 내부적으로 email로 가입된 사용자가 없으면 자동 회원가입합니다.
setClaims로 Map<String, Object>를 jwt 클레임으로 build하면, 앞서 builder에서 설정한 audience, subject등이 사라집니다. (claim을 통째로 덮어쓰기 때문) 따라서 Map의 요소를 직접 추가하도록 수정했습니다.
UserService내에 jwt로부터 user id를 추출하는 메소드 추가하였습니다.
Oauth 인증으로 로그인할 수 있도록 ApiUserController 생성
1. game 조작과 관련된 api 요청 시에는 CertificationInterceptor가 로그인 되어있는 상태인지 확인합니다. 2. preHandler에서 jwt를 사용해 로그인 되어있는지 확인하고, 로그인 되어있으면 HttpServletRequest에 user id를 담아 controller로 넘겨줍니다.
실수로 표준출력이 코드에 포함됨
볼 카운트 증가 후에 현재상황을 기록하도록 수정
배포 시 비밀번호 등의 설정파일에 환경변수를 사용하도록 수정
[BE] Oauth + JWT와 spring interceptor로 인증 구현 완료
현재 갖고있는 jwt가 유효한지 확인하는 API추가
This reverts commit 1632a7b. 1. jwt가 유효한지 확인하는 api가 필요없다고 판단하여 리버트함. 2. 기존의 GET game/status api에서 jwt 유효한지 확인하기 때문에 이를 확인하여 응답코드가 401이면 로그인되지 않은 상태라고 판단 가능, 추가로 400에러이면 게임에 들어가지 않은 상태이므로 게임방 목록을 보여주고, 200응답이면 게임에 들어간 상태로 앱이 종료된 것이므로 바로 게임중 화면을 보여주면 된다.
jwt 암호키를 코드에 노출시지 않도록 변경
ksundong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요. 아이작! 리뷰어 Dion입니다.
JWT같은 경우에는 정형화된 표준같은건 JWT Spec 정도고, 어떻게 보안 계층을 구현할 것인가는 조금 자유롭습니다.
여러가지 방식을 시도해보시고, 좋은 것 같은 방식을 선택해보는 것도 좋을 것 같아요.
spring interceptor에서 인증을 확인하면 HttpServletRequest에 setAttribute로 user_id를 추가하고, controller method에서 HttpServletRequest의 getAttribute로 user_id를 받게 구현해놓았습니다.
일반적으로 interceptor에서 생성한 값을 controller method로 전달할 때 이러한 방법을 사용하나요? 구글링으로는 이 방법이 최선인 것 같아 이렇게 구현하였는데, 컨트롤러 메소드가 HttpServletRequest에 종속성을 갖는 것 같아 괜찮은 방법인지 모르겠습니다.
좋은 질문입니다. 사실 Controller도 POJO처럼 얼마든지 만들어 줄 수 있는데, HttpServletRequest라는 Spring feature가 코드에 등장하면 조금 그렇겠죠.
해당 코드에 코멘트를 남겨두었는데, 한 번 확인해보시면 좋을 것 같습니다.
저는 그런 고민을 과정 동안 안했던 것 같은데 대단하네요.
코드를 보면서 아직 메서드를 분리하는 것이 조금 부족하다는 느낌을 받았습니다.
이 부분에 대해서 좀 더 신경써보시면 좋을 것 같아요.
또한 API 설계에서도 아쉬운 부분이 존재했습니다.
2주간 프로젝트 하느라 고생많으셨습니다~
| public String getAuthorizationValue() { | ||
| return this.tokenType + " " + this.accessToken; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| private HandlerInterceptor interceptor; | ||
|
|
||
| @Autowired | ||
| public WebMvcConfig(@Qualifier(value = "certificationInterceptor") HandlerInterceptor interceptor) { | ||
| this.interceptor = interceptor; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그냥 CertificationInterceptor를 직접 받아도 상관 없다는 생각이 듭니다.
| return ApiResult.succeed("OK"); | ||
| } | ||
|
|
||
| @PostMapping("/joining") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저라면, path에 join하려는 게임이 무엇인지도 포함해줬을 것 같아요.
| public ApiResult joinGame(@Valid @RequestBody JoinGameDTO joinGameDTO, HttpServletRequest request) { | ||
| long userId = (long) request.getAttribute("userId"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ServletRequest에서 필요한 데이터를 바인딩 하는 방법에 대해서 찾아보면 어떨까요?
https://www.baeldung.com/spring-mvc-custom-data-binder
https://reflectoring.io/spring-boot-argumentresolver/
https://codeboje.de/spring-mvc-argument-mapping-basics/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모든 응답을 ApiResult로 반환하는건 제 생각엔 그다지 좋지 않은 것 같습니다.
| public ApiResult joinGame(@Valid @RequestBody JoinGameDTO joinGameDTO, HttpServletRequest request) { | ||
| long userId = (long) request.getAttribute("userId"); | ||
| gameService.joinGame(userId, joinGameDTO.getGameId(), joinGameDTO.getMyVenue()); | ||
| return ApiResult.succeed("OK"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApiResult.ok() 같은걸 만들면 어떨까요?
|
|
||
| private User getUser(String email) { | ||
| return userRepository.findByEmail(email). | ||
| orElseThrow(() -> new NotFoundException("해당 email의 사용자는 존재하지 않습니다.")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception을 더 세분화해줘도 좋을 것 같아요.
| import java.util.Date; | ||
| import java.util.Map; | ||
|
|
||
| public class JwtUtil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
항상 Utility성 클래스는 인스턴스를 생성할 수 없게끔 private 기본 생성자를 만들어주세요~
| spring.datasource.url=${MYSQL_URL} | ||
| spring.datasource.username=${MYSQL_USER} | ||
| spring.datasource.password=${MYSQL_PASSWORD} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| User user = new User("isaac56@naver.com", ResourceServer.GITHUB); | ||
| User saved = userRepository.save(user); | ||
|
|
||
| Assertions.assertThat(user.getEmail()).isEqualTo(saved.getEmail()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertion은 정말 검증하고 싶은 값만 해주세요.
| boolean isWriteSignature = true; | ||
| try { | ||
| Claims claims = JwtUtil.getTokenData(jwt); | ||
| } catch (SignatureException ex) { | ||
| isWriteSignature = false; | ||
| } | ||
| Assertions.assertThat(isWriteSignature).isEqualTo(validated); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외가 발생하면 그냥 던지면 실패합니다.
ApiResult에 ok() 메소드를 생성하여, "OK"라는 임의의 String을 중복 사용하는 걸 제거했습니다.
조건문을 모아서 불필요한 if문 제거
안녕하세요 백엔드 아이작입니다.
코드 리뷰가 정말 많이 도움되는 것 같습니다.
항상 감사드립니다 😄