diff --git a/BE/baseball/build.gradle b/BE/baseball/build.gradle index 127b2dbe6..6a0521ab2 100644 --- a/BE/baseball/build.gradle +++ b/BE/baseball/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'io.jsonwebtoken:jjwt:0.9.1' compileOnly 'org.projectlombok:lombok' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' diff --git a/BE/baseball/src/main/java/team9/baseball/DTO/response/ApiResult.java b/BE/baseball/src/main/java/team9/baseball/DTO/response/ApiResult.java index 33d221630..01ea85ca3 100644 --- a/BE/baseball/src/main/java/team9/baseball/DTO/response/ApiResult.java +++ b/BE/baseball/src/main/java/team9/baseball/DTO/response/ApiResult.java @@ -1,10 +1,12 @@ package team9.baseball.DTO.response; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import lombok.Setter; @Getter @Setter +@JsonInclude(JsonInclude.Include.NON_NULL) public class ApiResult { private T data; private String error; @@ -14,6 +16,10 @@ private ApiResult(T data, String error) { this.error = error; } + public static ApiResult ok() { + return new ApiResult<>("OK", null); + } + public static ApiResult succeed(T data) { return new ApiResult(data, null); } diff --git a/BE/baseball/src/main/java/team9/baseball/DTO/response/GameDescriptionDTO.java b/BE/baseball/src/main/java/team9/baseball/DTO/response/GameDescriptionDTO.java index 60cee7909..e30605389 100644 --- a/BE/baseball/src/main/java/team9/baseball/DTO/response/GameDescriptionDTO.java +++ b/BE/baseball/src/main/java/team9/baseball/DTO/response/GameDescriptionDTO.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import team9.baseball.domain.enums.GameStatus; @Getter @Setter @@ -13,4 +14,5 @@ public class GameDescriptionDTO { private String homeTeam; private String awayUserEmail; private String homeUserEmail; + private GameStatus status; } diff --git a/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubEmailDTO.java b/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubEmailDTO.java new file mode 100644 index 000000000..b1ce5a397 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubEmailDTO.java @@ -0,0 +1,13 @@ +package team9.baseball.DTO.response.github; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GithubEmailDTO { + private String email; + private boolean primary; + private boolean verified; + private String visibility; +} diff --git a/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubTokenDTO.java b/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubTokenDTO.java new file mode 100644 index 000000000..1ab77794e --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/DTO/response/github/GithubTokenDTO.java @@ -0,0 +1,22 @@ +package team9.baseball.DTO.response.github; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GithubTokenDTO { + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("scope") + private String scope; + + public String getAuthorizationValue() { + return this.tokenType + " " + this.accessToken; + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/config/WebMvcConfig.java b/BE/baseball/src/main/java/team9/baseball/config/WebMvcConfig.java new file mode 100644 index 000000000..25bada9b7 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/config/WebMvcConfig.java @@ -0,0 +1,26 @@ +package team9.baseball.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + private HandlerInterceptor interceptor; + + @Autowired + public WebMvcConfig(@Qualifier(value = "certificationInterceptor") HandlerInterceptor interceptor) { + this.interceptor = interceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor) + .addPathPatterns("/**") + .excludePathPatterns("/user/**/") + .excludePathPatterns("/game/list"); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/controller/ApiGameController.java b/BE/baseball/src/main/java/team9/baseball/controller/ApiGameController.java index 3b62cdccd..a3415812c 100644 --- a/BE/baseball/src/main/java/team9/baseball/controller/ApiGameController.java +++ b/BE/baseball/src/main/java/team9/baseball/controller/ApiGameController.java @@ -8,6 +8,7 @@ import team9.baseball.DTO.response.ApiResult; import team9.baseball.service.GameService; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; @RestController @@ -27,29 +28,40 @@ public ApiResult getGameDescriptions() { @PostMapping public ApiResult createGame(@RequestBody CreateGameDTO createGameDTO) { - gameService.createNewGame(1l, createGameDTO.getAwayTeamId(), createGameDTO.getHomeTeamId()); - return ApiResult.succeed("OK"); + gameService.createNewGame(createGameDTO.getAwayTeamId(), createGameDTO.getHomeTeamId()); + return ApiResult.ok(); } @PostMapping("/joining") - public ApiResult joinGame(@Valid @RequestBody JoinGameDTO joinGameDTO) { - gameService.joinGame(1l, joinGameDTO.getGameId(), joinGameDTO.getMyVenue()); - return ApiResult.succeed("OK"); + public ApiResult joinGame(@Valid @RequestBody JoinGameDTO joinGameDTO, HttpServletRequest request) { + long userId = (long) request.getAttribute("userId"); + gameService.joinGame(userId, joinGameDTO.getGameId(), joinGameDTO.getMyVenue()); + return ApiResult.ok(); + } + + @DeleteMapping("/joining") + public ApiResult quitGame(HttpServletRequest request) { + long userId = (long) request.getAttribute("userId"); + gameService.quitGame(userId); + return ApiResult.ok(); } @GetMapping("/status") - public ApiResult getCurrentGameStatus() { - return ApiResult.succeed(gameService.getCurrentGameStatus(1l)); + public ApiResult getCurrentGameStatus(HttpServletRequest request) { + long userId = (long) request.getAttribute("userId"); + return ApiResult.succeed(gameService.getCurrentGameStatus(userId)); } @PostMapping("/status/pitch-result") - public ApiResult pitch(@RequestBody PitchResultDTO pitchResultDTO) { - gameService.applyPitchResult(1l, pitchResultDTO.getPitchResult()); - return ApiResult.succeed("OK"); + public ApiResult pitch(@RequestBody PitchResultDTO pitchResultDTO, HttpServletRequest request) { + long userId = (long) request.getAttribute("userId"); + gameService.applyPitchResult(userId, pitchResultDTO.getPitchResult()); + return ApiResult.ok(); } @GetMapping("/history") - public ApiResult getCurrentGameHistory() { - return ApiResult.succeed(gameService.getCurrentGameHistory(1l)); + public ApiResult getCurrentGameHistory(HttpServletRequest request) { + long userId = (long) request.getAttribute("userId"); + return ApiResult.succeed(gameService.getCurrentGameHistory(userId)); } } diff --git a/BE/baseball/src/main/java/team9/baseball/controller/ApiUserController.java b/BE/baseball/src/main/java/team9/baseball/controller/ApiUserController.java new file mode 100644 index 000000000..2f28dcd5c --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/controller/ApiUserController.java @@ -0,0 +1,31 @@ +package team9.baseball.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import team9.baseball.DTO.response.ApiResult; +import team9.baseball.domain.enums.ResourceServer; +import team9.baseball.service.OauthService; +import team9.baseball.service.UserService; + +@RestController +@RequestMapping("/user") +public class ApiUserController { + private final UserService userService; + private final OauthService oauthService; + + @Autowired + public ApiUserController(UserService userService, OauthService oauthService) { + this.userService = userService; + this.oauthService = oauthService; + } + + @GetMapping("/login/oauth/github") + public ApiResult loginUsingGithub(String code) { + String accessToken = oauthService.getAccessToken(code); + String email = oauthService.getEmail(accessToken); + + return ApiResult.succeed(userService.signIn(email, ResourceServer.GITHUB)); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/game/Game.java b/BE/baseball/src/main/java/team9/baseball/domain/aggregate/game/Game.java index 223e81cd2..39928afce 100644 --- a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/game/Game.java +++ b/BE/baseball/src/main/java/team9/baseball/domain/aggregate/game/Game.java @@ -7,8 +7,10 @@ import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.MappedCollection; import team9.baseball.domain.aggregate.team.Team; +import team9.baseball.domain.enums.GameStatus; import team9.baseball.domain.enums.Halves; import team9.baseball.domain.enums.PitchResult; +import team9.baseball.exception.BadStatusException; import team9.baseball.exception.NotFoundException; import java.util.HashMap; @@ -45,12 +47,17 @@ public class Game { private int outCount; + private GameStatus status; + @MappedCollection(idColumn = "game_id", keyColumn = "key_in_game") private Map battingHistoryMap = new HashMap<>(); @MappedCollection(idColumn = "game_id", keyColumn = "key_in_game") private Map inningMap = new HashMap<>(); + private final int STRIKE_OUT_COUNT = 3; + private final int INNING_OUT_COUNT = 3; + public Game(Team awayTeam, Team homeTeam) { this.awayTeamId = awayTeam.getId(); this.homeTeamId = homeTeam.getId(); @@ -63,6 +70,8 @@ public Game(Team awayTeam, Team homeTeam) { this.currentInning = 1; this.currentHalves = Halves.TOP; this.inningMap.put(Inning.acquireKeyInGame(currentInning, currentHalves), new Inning(currentInning, currentHalves)); + + this.status = GameStatus.WAITING; } private void initializeBattingHistory(Team team) { @@ -73,30 +82,42 @@ private void initializeBattingHistory(Team team) { } } + public void checkWaiting() { + if (this.status != GameStatus.WAITING) { + throw new BadStatusException("대기중인 게임이 아닙니다."); + } + } + + public void checkPlaying() { + if (this.status != GameStatus.PLAYING) { + throw new BadStatusException("진행중인 게임이 아닙니다."); + } + } + public void proceedStrike(Team awayTeam, Team homeTeam) { + //카운트 증가 + this.strikeCount++; //기록할 pitch history 생성 PitchHistory pitchHistory = new PitchHistory(acquireDefenseTeamId(), pitcherUniformNumber, acquireAttackTeamId(), batterUniformNumber, PitchResult.STRIKE, this.strikeCount, this.ballCount); //현재 이닝에 pitch history 기록 acquireCurrentInning().pitchHistoryList.add(pitchHistory); - //카운트 증가 - this.strikeCount++; //삼진 아웃 처리 - if (strikeCount == 3) { + if (strikeCount == STRIKE_OUT_COUNT) { proceedOut(awayTeam, homeTeam); } } public void proceedBall(Team awayTeam, Team homeTeam) { + //카운트 증가 + this.ballCount++; //기록할 pitch history 생성 PitchHistory pitchHistory = new PitchHistory(acquireDefenseTeamId(), pitcherUniformNumber, acquireAttackTeamId(), batterUniformNumber, PitchResult.BALL, this.strikeCount, this.ballCount); //현재 이닝에 pitch history 기록 acquireCurrentInning().pitchHistoryList.add(pitchHistory); - //카운트 증가 - this.ballCount++; //볼넷일 경우 출루하고 다음 타자 등판 if (ballCount == 4) { sendBatterOnBase(); @@ -191,7 +212,7 @@ private void proceedOut(Team awayTeam, Team homeTeam) { battingHistory.plusOut(); //3회 아웃이면 다음이닝으로 변경 - if (outCount == 3) { + if (outCount == INNING_OUT_COUNT) { goToNextInning(awayTeam, homeTeam); return; } @@ -214,10 +235,19 @@ private void sendBatterOnBase() { } private void goToNextInning(Team awayTeam, Team homeTeam) { + //게임 종료 상황이면 다음이닝으로 넘어가지 않고 게임종료 + if (isExited()) { + this.status = GameStatus.EXITED; + return; + } + //카운트 초기화 this.strikeCount = 0; this.ballCount = 0; this.outCount = 0; + this.base1UniformNumber = null; + this.base2UniformNumber = null; + this.base3UniformNumber = null; //다음 이닝으로 변경 if (this.currentHalves == Halves.BOTTOM) { @@ -240,6 +270,19 @@ private void goToNextInning(Team awayTeam, Team homeTeam) { sendBatterOnPlate(attackTeam.getId(), nextBatterUniformNumber); } + private boolean isScoreDifferent() { + return getTotalScore(Halves.TOP) != getTotalScore(Halves.BOTTOM); + } + + private boolean isExited() { + if ((currentInning == 9 && currentHalves == Halves.BOTTOM && isScoreDifferent()) + || (currentInning == 12 && currentHalves == Halves.BOTTOM)) { + return true; + } + + return false; + } + private void sendBatterOnPlate(int batterTeamId, int nextBatterUniformNumber) { //카운트 초기화 this.strikeCount = 0; diff --git a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/OauthAccessToken.java b/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/OauthAccessToken.java deleted file mode 100644 index ba9198c54..000000000 --- a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/OauthAccessToken.java +++ /dev/null @@ -1,25 +0,0 @@ -package team9.baseball.domain.aggregate.user; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.annotation.Id; -import team9.baseball.domain.enums.ResourceOwner; - -@Getter -@Setter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class OauthAccessToken { - @Id - private Long id; - - private String resourceOwner; - - private String accessToken; - - public OauthAccessToken(ResourceOwner resourceOwner, String accessToken) { - this.resourceOwner = resourceOwner.name(); - this.accessToken = accessToken; - } -} diff --git a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/User.java b/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/User.java index 8d5fe2da2..6b39edc31 100644 --- a/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/User.java +++ b/BE/baseball/src/main/java/team9/baseball/domain/aggregate/user/User.java @@ -5,13 +5,9 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.annotation.Id; -import org.springframework.data.relational.core.mapping.MappedCollection; -import team9.baseball.domain.enums.ResourceOwner; +import team9.baseball.domain.enums.ResourceServer; import team9.baseball.domain.enums.Venue; -import team9.baseball.exception.NotFoundException; - -import java.util.HashMap; -import java.util.Map; +import team9.baseball.exception.BadStatusException; @Getter @Setter @@ -26,19 +22,22 @@ public class User { private Venue currentGameVenue; - @MappedCollection(idColumn = "user_id", keyColumn = "resource_owner") - private Map oauthAccessTokenMap = new HashMap<>(); + private ResourceServer oauthResourceServer; - public User(String email, OauthAccessToken oauthAccessToken) { + public User(String email, ResourceServer oauthResourceServer) { this.email = email; - this.oauthAccessTokenMap.put(oauthAccessToken.getResourceOwner(), oauthAccessToken); + this.oauthResourceServer = oauthResourceServer; + } + + public void checkUserJoining() { + if (this.currentGameId == null) { + throw new BadStatusException(id + "사용자는 게임중이 아닙니다."); + } } - public String getAccessToken(ResourceOwner resourceOwner) { - OauthAccessToken oauthAccessToken = oauthAccessTokenMap.getOrDefault(resourceOwner.name(), null); - if (oauthAccessToken == null) { - throw new NotFoundException(resourceOwner.name() + "의 access token이 존재하지 않습니다."); + public void checkUserNotJoining() { + if (this.currentGameId != null) { + throw new BadStatusException(id + "사용자는 이미 게임중입니다."); } - return oauthAccessToken.getAccessToken(); } } diff --git a/BE/baseball/src/main/java/team9/baseball/domain/enums/GameStatus.java b/BE/baseball/src/main/java/team9/baseball/domain/enums/GameStatus.java new file mode 100644 index 000000000..ec77e1d90 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/domain/enums/GameStatus.java @@ -0,0 +1,7 @@ +package team9.baseball.domain.enums; + +public enum GameStatus { + WAITING, + PLAYING, + EXITED +} diff --git a/BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceOwner.java b/BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceServer.java similarity index 63% rename from BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceOwner.java rename to BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceServer.java index 5267977c7..608ea71cd 100644 --- a/BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceOwner.java +++ b/BE/baseball/src/main/java/team9/baseball/domain/enums/ResourceServer.java @@ -1,5 +1,5 @@ package team9.baseball.domain.enums; -public enum ResourceOwner { +public enum ResourceServer { GITHUB } diff --git a/BE/baseball/src/main/java/team9/baseball/domain/enums/Venue.java b/BE/baseball/src/main/java/team9/baseball/domain/enums/Venue.java index fe1e46faa..aa612febb 100644 --- a/BE/baseball/src/main/java/team9/baseball/domain/enums/Venue.java +++ b/BE/baseball/src/main/java/team9/baseball/domain/enums/Venue.java @@ -10,4 +10,11 @@ public Halves getHalves() { } return Halves.BOTTOM; } + + public Venue getOtherVenue() { + if (this == AWAY) { + return HOME; + } + return AWAY; + } } diff --git a/BE/baseball/src/main/java/team9/baseball/exception/BadStatusException.java b/BE/baseball/src/main/java/team9/baseball/exception/BadStatusException.java new file mode 100644 index 000000000..f0f1b3ebf --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/exception/BadStatusException.java @@ -0,0 +1,15 @@ +package team9.baseball.exception; + +public class BadStatusException extends RuntimeException { + public BadStatusException() { + super("객체의 상태가 적절하지 않습니다."); + } + + public BadStatusException(String message) { + super(message); + } + + public BadStatusException(Throwable cause) { + super(cause); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/exception/OauthException.java b/BE/baseball/src/main/java/team9/baseball/exception/OauthException.java new file mode 100644 index 000000000..a5c3a58e8 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/exception/OauthException.java @@ -0,0 +1,15 @@ +package team9.baseball.exception; + +public class OauthException extends RuntimeException { + public OauthException() { + super("Oauth 인증을 할 수 없습니다."); + } + + public OauthException(String message) { + super(message); + } + + public OauthException(Throwable cause) { + super(cause); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/exception/UnauthorizedException.java b/BE/baseball/src/main/java/team9/baseball/exception/UnauthorizedException.java new file mode 100644 index 000000000..0cdedede0 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/exception/UnauthorizedException.java @@ -0,0 +1,15 @@ +package team9.baseball.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException() { + super("권한이 없습니다."); + } + + public UnauthorizedException(String message) { + super(message); + } + + public UnauthorizedException(Throwable cause) { + super(cause); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/exceptionHandler/GlobalExceptionHandler.java b/BE/baseball/src/main/java/team9/baseball/exceptionHandler/GlobalExceptionHandler.java index 28a23aad0..6db3ad6b9 100644 --- a/BE/baseball/src/main/java/team9/baseball/exceptionHandler/GlobalExceptionHandler.java +++ b/BE/baseball/src/main/java/team9/baseball/exceptionHandler/GlobalExceptionHandler.java @@ -1,23 +1,42 @@ package team9.baseball.exceptionHandler; +import io.jsonwebtoken.JwtException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import team9.baseball.DTO.response.ApiResult; +import team9.baseball.exception.BadStatusException; import team9.baseball.exception.NotFoundException; +import team9.baseball.exception.OauthException; +import team9.baseball.exception.UnauthorizedException; @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(RuntimeException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiResult runtimeExcpetion(Exception ex) { + @ExceptionHandler(BadStatusException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult badStatusException(BadStatusException ex) { return ApiResult.failed(ex); } @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) - public ApiResult notFoundException(Exception ex) { + public ApiResult notFoundException(NotFoundException ex) { + return ApiResult.failed(ex); + } + + @ExceptionHandler(JwtException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ApiResult signatureException(JwtException exception) { + return ApiResult.failed("유효한 access token 이 아닙니다."); + } + + @ExceptionHandler({ + UnauthorizedException.class, + OauthException.class + }) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ApiResult oauthException(Exception ex) { return ApiResult.failed(ex); } } diff --git a/BE/baseball/src/main/java/team9/baseball/interceptor/CertificationInterceptor.java b/BE/baseball/src/main/java/team9/baseball/interceptor/CertificationInterceptor.java new file mode 100644 index 000000000..2418b9714 --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/interceptor/CertificationInterceptor.java @@ -0,0 +1,43 @@ +package team9.baseball.interceptor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import team9.baseball.exception.UnauthorizedException; +import team9.baseball.service.UserService; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class CertificationInterceptor implements HandlerInterceptor { + private final UserService userService; + + @Autowired + public CertificationInterceptor(UserService userService) { + this.userService = userService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String jwt = request.getHeader("Authorization"); + if (jwt == null) { + throw new UnauthorizedException("Authorization 코드가 존재하지 않습니다."); + } + + request.setAttribute("userId", userService.getUserIdFromJwt(jwt)); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/repository/CustomGameRepository.java b/BE/baseball/src/main/java/team9/baseball/repository/CustomGameRepository.java index 5699d296b..55d5f93c1 100644 --- a/BE/baseball/src/main/java/team9/baseball/repository/CustomGameRepository.java +++ b/BE/baseball/src/main/java/team9/baseball/repository/CustomGameRepository.java @@ -8,9 +8,9 @@ @Repository public interface CustomGameRepository { - @Query("select SG.id, SG.away_team, SG.home_team, away_user.email as `away_user_email`, home_user.email as `home_user_email` from " + + @Query("select SG.id, SG.away_team, SG.home_team, away_user.email as `away_user_email`, home_user.email as `home_user_email`, SG.status from " + "( " + - "select G.id as `id`, T1.name as `away_team`, T2.name as `home_team` from game G " + + "select G.id as `id`, T1.name as `away_team`, T2.name as `home_team`, G.status as `status` from game G " + "left join team T1 on G.away_team_id = T1.id " + "left join team T2 on G.home_team_id = T2.id " + ") SG " + diff --git a/BE/baseball/src/main/java/team9/baseball/repository/UserRepository.java b/BE/baseball/src/main/java/team9/baseball/repository/UserRepository.java index edda651c0..35d4ebff5 100644 --- a/BE/baseball/src/main/java/team9/baseball/repository/UserRepository.java +++ b/BE/baseball/src/main/java/team9/baseball/repository/UserRepository.java @@ -4,6 +4,16 @@ import team9.baseball.domain.aggregate.user.User; import team9.baseball.domain.enums.Venue; +import java.util.Optional; + public interface UserRepository extends CrudRepository { + boolean existsByEmail(String email); + + Optional findByEmail(String email); + + boolean existsByCurrentGameId(Long currentGameId); + boolean existsByCurrentGameIdAndCurrentGameVenue(Long currentGameId, Venue currentGameVenue); + + Optional findByIdAndEmailAndOauthResourceServer(Long id, String email, String oauthResourceServer); } diff --git a/BE/baseball/src/main/java/team9/baseball/service/GameService.java b/BE/baseball/src/main/java/team9/baseball/service/GameService.java index 8ba2b306a..e78b39339 100644 --- a/BE/baseball/src/main/java/team9/baseball/service/GameService.java +++ b/BE/baseball/src/main/java/team9/baseball/service/GameService.java @@ -8,8 +8,10 @@ import team9.baseball.domain.aggregate.game.Game; import team9.baseball.domain.aggregate.team.Team; import team9.baseball.domain.aggregate.user.User; +import team9.baseball.domain.enums.GameStatus; import team9.baseball.domain.enums.PitchResult; import team9.baseball.domain.enums.Venue; +import team9.baseball.exception.BadStatusException; import team9.baseball.exception.NotFoundException; import team9.baseball.repository.GameRepository; import team9.baseball.repository.TeamRepository; @@ -32,16 +34,17 @@ public GameService(GameRepository gameRepository, TeamRepository teamRepository, public void applyPitchResult(long userId, PitchResult pitchResult) { User user = getUser(userId); - if (user.getCurrentGameId() == null) { - throw new RuntimeException(userId + "사용자는 게임중이 아닙니다."); - } + user.checkUserJoining(); + Game game = getGame(user.getCurrentGameId()); + game.checkPlaying(); + Team awayTeam = getTeam(game.getAwayTeamId()); Team homeTeam = getTeam(game.getHomeTeamId()); //현재 내가 공격팀이면 공을 던질 수 없다. if (game.getCurrentHalves() == user.getCurrentGameVenue().getHalves()) { - throw new RuntimeException(userId + "번 사용자는 현재 공격팀입니다."); + throw new BadStatusException(userId + "번 사용자는 현재 공격팀입니다."); } switch (pitchResult) { @@ -60,9 +63,8 @@ public void applyPitchResult(long userId, PitchResult pitchResult) { public GameStatusDTO getCurrentGameStatus(long userId) { User user = getUser(userId); - if (user.getCurrentGameId() == null) { - throw new RuntimeException(userId + "사용자는 게임중이 아닙니다."); - } + user.checkUserJoining(); + Game game = getGame(user.getCurrentGameId()); Team awayTeam = getTeam(game.getAwayTeamId()); Team homeTeam = getTeam(game.getHomeTeamId()); @@ -72,9 +74,8 @@ public GameStatusDTO getCurrentGameStatus(long userId) { public GameHistoryDTO getCurrentGameHistory(long userId) { User user = getUser(userId); - if (user.getCurrentGameId() == null) { - throw new RuntimeException(userId + "사용자는 게임중이 아닙니다."); - } + user.checkUserJoining(); + Game game = getGame(user.getCurrentGameId()); Team awayTeam = getTeam(game.getAwayTeamId()); Team homeTeam = getTeam(game.getHomeTeamId()); @@ -82,8 +83,7 @@ public GameHistoryDTO getCurrentGameHistory(long userId) { return GameHistoryDTO.of(game, awayTeam, homeTeam); } - public void createNewGame(long userId, int awayTeamId, int homeTeamId) { - User user = getUser(userId); + public void createNewGame(int awayTeamId, int homeTeamId) { Team awayTeam = getTeam(awayTeamId); Team homeTeam = getTeam(homeTeamId); @@ -93,17 +93,44 @@ public void createNewGame(long userId, int awayTeamId, int homeTeamId) { public void joinGame(long userId, long gameId, Venue venue) { User user = getUser(userId); - if (user.getCurrentGameId() != null) { - throw new RuntimeException(userId + "사용자는 이미 게임중입니다."); - } - Game game = getGame(gameId); + user.checkUserNotJoining(); + if (userRepository.existsByCurrentGameIdAndCurrentGameVenue(gameId, venue)) { - throw new RuntimeException(gameId + "번 게임의 " + venue + "팀은 다른 사용자가 참가했습니다."); + throw new BadStatusException(gameId + "번 게임의 " + venue + "팀은 다른 사용자가 참가했습니다."); } + Game game = getGame(gameId); + game.checkWaiting(); + user.setCurrentGameId(gameId); user.setCurrentGameVenue(venue); userRepository.save(user); + + //상대팀도 유저가 들어와있으면 게임 시작 + if (userRepository.existsByCurrentGameIdAndCurrentGameVenue(gameId, venue.getOtherVenue())) { + + game.setStatus(GameStatus.PLAYING); + gameRepository.save(game); + } + } + + public void quitGame(long userId) { + User user = getUser(userId); + user.checkUserJoining(); + long currentGameId = user.getCurrentGameId(); + + user.setCurrentGameId(null); + user.setCurrentGameVenue(null); + userRepository.save(user); + + Game game = getGame(currentGameId); + game.setStatus(GameStatus.EXITED); + gameRepository.save(game); + + //game에 참가중인 유저가 모두 나갔을 경우 게임방 삭제 + if (!userRepository.existsByCurrentGameId(game.getId())) { + gameRepository.delete(game); + } } public List getAllGameList() { diff --git a/BE/baseball/src/main/java/team9/baseball/service/GithubOauthService.java b/BE/baseball/src/main/java/team9/baseball/service/GithubOauthService.java new file mode 100644 index 000000000..8f30ab8bf --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/service/GithubOauthService.java @@ -0,0 +1,90 @@ +package team9.baseball.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import team9.baseball.DTO.response.github.GithubEmailDTO; +import team9.baseball.DTO.response.github.GithubTokenDTO; +import team9.baseball.exception.OauthException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class GithubOauthService implements OauthService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final String clientId = System.getenv("GITHUB_OAUTH_CLIENT_ID"); + private final String clientSecret = System.getenv("GITHUB_OAUTH_CLIENT_PASSWORD"); + + //https://github.com/login/oauth/authorize?client_id=6c8240ec73fc69e81d92&scope=user:email + @Override + public String getAccessToken(String authorizationCode) { + String url = "https://github.com/login/oauth/access_token"; + logger.debug("authorization code: " + authorizationCode); + MultiValueMap headers = new LinkedMultiValueMap<>(); + Map header = new HashMap<>(); + header.put("Accept", "application/json"); //json 형식으로 응답 받음 + headers.setAll(header); + + MultiValueMap requestPayloads = new LinkedMultiValueMap<>(); + Map requestPayload = new HashMap<>(); + requestPayload.put("client_id", clientId); + requestPayload.put("client_secret", clientSecret); + requestPayload.put("code", authorizationCode); + requestPayloads.setAll(requestPayload); + + HttpEntity request = new HttpEntity<>(requestPayloads, headers); + ResponseEntity response = new RestTemplate().postForEntity(url, request, GithubTokenDTO.class); //미리 정의해둔 GithubToken 클래스 형태로 Response Body를 파싱해서 담아서 리턴 + String accessToken = response.getBody().getAuthorizationValue(); + + if (accessToken == null) { + throw new OauthException("잘못된 github Authorization code 입니다."); + } + + return response.getBody().getAuthorizationValue(); + } + + @Override + public String getEmail(String accessToken) { + String url = "https://api.github.com/user/emails"; + + MultiValueMap headers = new LinkedMultiValueMap<>(); + Map header = new HashMap<>(); + header.put("Accept", "application/json"); //json 형식으로 응답 받음 + header.put("Authorization", accessToken); + headers.setAll(header); + + HttpEntity request = new HttpEntity<>(headers); + ResponseEntity response; + try { + response = new RestTemplate().exchange(url, HttpMethod.GET, request, String.class); + } catch (HttpClientErrorException ex) { + throw new OauthException("유효한 github access token 이 아닙니다."); + } + + ObjectMapper objectMapper = new ObjectMapper(); + List emails = null; + try { + emails = objectMapper.readValue(response.getBody(), new TypeReference>() { + }); + } catch (IOException ex) { + throw new OauthException("github email 정보를 가져올 수 없습니다."); + } + + if (emails == null || emails.isEmpty()) { + throw new OauthException("github email 정보를 가져올 수 없습니다."); + } + return emails.get(0).getEmail(); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/service/OauthService.java b/BE/baseball/src/main/java/team9/baseball/service/OauthService.java new file mode 100644 index 000000000..af222030b --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/service/OauthService.java @@ -0,0 +1,10 @@ +package team9.baseball.service; + +import org.springframework.stereotype.Service; + +@Service +public interface OauthService { + String getAccessToken(String authorizationCode); + + String getEmail(String accessToken); +} diff --git a/BE/baseball/src/main/java/team9/baseball/service/UserService.java b/BE/baseball/src/main/java/team9/baseball/service/UserService.java new file mode 100644 index 000000000..ee0c3693d --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/service/UserService.java @@ -0,0 +1,73 @@ +package team9.baseball.service; + +import io.jsonwebtoken.Claims; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import team9.baseball.domain.aggregate.user.User; +import team9.baseball.domain.enums.ResourceServer; +import team9.baseball.exception.NotFoundException; +import team9.baseball.exception.UnauthorizedException; +import team9.baseball.repository.UserRepository; +import team9.baseball.util.JwtUtil; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class UserService { + private static final String JWT_SIGN_IN_SUBJECT = "sign_in"; + private static final String JWT_ID = "id"; + private static final String JWT_EMAIL = "email"; + private static final String JWT_RESOURCE_SERVER = "resource_server"; + + private UserRepository userRepository; + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public String signIn(String email, ResourceServer resourceServer) { + //회원 정보가 없으면 자동 회원가입 + if (!userRepository.existsByEmail(email)) { + signUp(email, resourceServer); + } + + User user = getUser(email); + return getJsonWebToken(user); + } + + public Long getUserIdFromJwt(String jwt) { + Claims claims = JwtUtil.getTokenData(jwt); + if (!JWT_SIGN_IN_SUBJECT.equals(claims.getSubject())) { + throw new UnauthorizedException("토큰의 주제가 로그인 확인과 다릅니다."); + } + Long id = claims.get(JWT_ID, Long.class); + String email = claims.get(JWT_EMAIL, String.class); + String resourceServer = claims.get(JWT_RESOURCE_SERVER, String.class); + + User user = userRepository.findByIdAndEmailAndOauthResourceServer(id, email, resourceServer) + .orElseThrow(() -> new UnauthorizedException("토큰 정보에 해당하는 사용자를 찾을 수 없습니다.")); + + return user.getId(); + } + + private String getJsonWebToken(User user) { + Map privateClaims = new HashMap<>(); + privateClaims.put(JWT_ID, user.getId()); + privateClaims.put(JWT_EMAIL, user.getEmail()); + privateClaims.put(JWT_RESOURCE_SERVER, user.getOauthResourceServer()); + + return JwtUtil.createToken(JWT_SIGN_IN_SUBJECT, "User", privateClaims, 100); + } + + private void signUp(String email, ResourceServer resourceServer) { + User user = new User(email, resourceServer); + userRepository.save(user); + } + + private User getUser(String email) { + return userRepository.findByEmail(email). + orElseThrow(() -> new NotFoundException("해당 email의 사용자는 존재하지 않습니다.")); + } +} diff --git a/BE/baseball/src/main/java/team9/baseball/util/JwtUtil.java b/BE/baseball/src/main/java/team9/baseball/util/JwtUtil.java new file mode 100644 index 000000000..badbe6f6b --- /dev/null +++ b/BE/baseball/src/main/java/team9/baseball/util/JwtUtil.java @@ -0,0 +1,54 @@ +package team9.baseball.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import java.security.Key; +import java.util.Date; +import java.util.Map; + +public class JwtUtil { + + private static final String SECRET_KEY = System.getenv("JWT_SECRET_KEY"); + + public static String createToken(String subject, String audience, Map privateClaims, int expiredMinute) { + + long ttlMillis = (expiredMinute * 60 * 1000); + + if (ttlMillis <= 0) { + throw new RuntimeException("JWT 유효시간은 0보다 작거나 같을 수 없습니다."); + } + + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; + + byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY); + Key signingKey = new SecretKeySpec(secretKeyBytes, signatureAlgorithm.getJcaName()); + JwtBuilder builder = Jwts.builder() + .setSubject(subject) + .setIssuer("isaac.baseball.api") + .setAudience(audience) + .setHeaderParam("type", "JWT") + .signWith(signatureAlgorithm, signingKey); + + for (Map.Entry claim : privateClaims.entrySet()) { + builder.claim(claim.getKey(), claim.getValue()); + } + + + long nowMillis = System.currentTimeMillis(); + builder.setExpiration(new Date(nowMillis + ttlMillis)); + return builder.compact(); + } + + public static Claims getTokenData(String token) { + Claims claims = Jwts.parser() + .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY)) + .parseClaimsJws(token).getBody(); + + return claims; + } +} diff --git a/BE/baseball/src/main/resources/application.properties b/BE/baseball/src/main/resources/application.properties index 648723da5..23a28758c 100644 --- a/BE/baseball/src/main/resources/application.properties +++ b/BE/baseball/src/main/resources/application.properties @@ -1,7 +1,7 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/baseball -spring.datasource.username=baseball -spring.datasource.password=test -spring.datasource.initialization-mode=always +spring.datasource.url=${MYSQL_URL} +spring.datasource.username=${MYSQL_USER} +spring.datasource.password=${MYSQL_PASSWORD} +spring.datasource.initialization-mode=never logging.level.team9=debug logging.level.sql=debug spring.jackson.property-naming-strategy=SNAKE_CASE diff --git a/BE/baseball/src/main/resources/data.sql b/BE/baseball/src/main/resources/data.sql index 81c5e2541..0bafcad7f 100644 --- a/BE/baseball/src/main/resources/data.sql +++ b/BE/baseball/src/main/resources/data.sql @@ -12,6 +12,6 @@ INSERT INTO player (team_id, name, uniform_number) VALUES (2, '크롱', 3); INSERT INTO player (team_id, name, uniform_number) VALUES (2, '세라', 4); INSERT INTO player (team_id, name, uniform_number) VALUES (2, '헤드', 5); -INSERT INTO `user` (email) VALUES ('isaac@naver.com'); -INSERT INTO `user` (email) VALUES ('soo@naver.com'); -INSERT INTO `user` (email) VALUES ('song@naver.com'); +INSERT INTO `user` (email, oauth_resource_server) VALUES ('isaac@naver.com', 'GITHUB'); +INSERT INTO `user` (email, oauth_resource_server) VALUES ('soo@naver.com', 'GITHUB'); +INSERT INTO `user` (email, oauth_resource_server) VALUES ('song@naver.com', 'GITHUB'); diff --git a/BE/baseball/src/main/resources/schema.sql b/BE/baseball/src/main/resources/schema.sql index e7cf64e15..69f631722 100644 --- a/BE/baseball/src/main/resources/schema.sql +++ b/BE/baseball/src/main/resources/schema.sql @@ -1,5 +1,5 @@ -- MySQL Script generated by MySQL Workbench --- Fri May 7 10:28:19 2021 +-- Tue May 11 02:25:52 2021 -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering @@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS `baseball`.`game` ( `strike_count` INT NOT NULL, `ball_count` INT NOT NULL, `out_count` INT NOT NULL, + `status` VARCHAR(45) NOT NULL, PRIMARY KEY (`id`), INDEX `fk_game_team2_idx` (`away_team_id` ASC), INDEX `fk_game_team1_idx` (`home_team_id` ASC), @@ -75,6 +76,7 @@ CREATE TABLE IF NOT EXISTS `baseball`.`user` ( `email` VARCHAR(45) NOT NULL, `current_game_id` BIGINT NULL, `current_game_venue` CHAR(4) NULL, + `oauth_resource_server` VARCHAR(45) NOT NULL, PRIMARY KEY (`id`), INDEX `fk_user_game1_idx` (`current_game_id` ASC), UNIQUE INDEX `email_UNIQUE` (`email` ASC), @@ -196,27 +198,6 @@ CREATE TABLE IF NOT EXISTS `baseball`.`batting_history` ( ENGINE = InnoDB; --- ----------------------------------------------------- --- Table `baseball`.`oauth_access_token` --- ----------------------------------------------------- -DROP TABLE IF EXISTS `baseball`.`oauth_access_token` ; - -CREATE TABLE IF NOT EXISTS `baseball`.`oauth_access_token` ( - `id` BIGINT NOT NULL AUTO_INCREMENT, - `user_id` BIGINT NOT NULL, - `resource_owner` VARCHAR(45) NOT NULL, - `access_token` VARCHAR(100) NOT NULL, - PRIMARY KEY (`id`), - INDEX `fk_oauth_access_token_user1_idx` (`user_id` ASC), - INDEX `uk_user_id_resource_owner` (`user_id` ASC, `resource_owner` ASC), - CONSTRAINT `fk_oauth_access_token_user1` - FOREIGN KEY (`user_id`) - REFERENCES `baseball`.`user` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) - ENGINE = InnoDB; - - SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/BE/baseball/src/test/java/team9/baseball/repository/UserRepositoryTest.java b/BE/baseball/src/test/java/team9/baseball/repository/UserRepositoryTest.java index 3384facf9..13e639e16 100644 --- a/BE/baseball/src/test/java/team9/baseball/repository/UserRepositoryTest.java +++ b/BE/baseball/src/test/java/team9/baseball/repository/UserRepositoryTest.java @@ -5,9 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; -import team9.baseball.domain.aggregate.user.OauthAccessToken; import team9.baseball.domain.aggregate.user.User; -import team9.baseball.domain.enums.ResourceOwner; +import team9.baseball.domain.enums.ResourceServer; @SpringBootTest @Transactional @@ -21,15 +20,12 @@ public UserRepositoryTest(UserRepository userRepository) { @Test public void 유저생성조회테스트() { - OauthAccessToken oauthAccessToken = new OauthAccessToken(ResourceOwner.GITHUB, "TEST"); - User user = new User("isaac56@naver.com", oauthAccessToken); + User user = new User("isaac56@naver.com", ResourceServer.GITHUB); User saved = userRepository.save(user); Assertions.assertThat(user.getEmail()).isEqualTo(saved.getEmail()); User found = userRepository.findById(saved.getId()).get(); Assertions.assertThat(found.getEmail()).isEqualTo(saved.getEmail()); - - Assertions.assertThat(found.getAccessToken(ResourceOwner.GITHUB)).isEqualTo(oauthAccessToken.getAccessToken()); } } diff --git a/BE/baseball/src/test/java/team9/baseball/util/JwtUtilTest.java b/BE/baseball/src/test/java/team9/baseball/util/JwtUtilTest.java new file mode 100644 index 000000000..6337b5861 --- /dev/null +++ b/BE/baseball/src/test/java/team9/baseball/util/JwtUtilTest.java @@ -0,0 +1,52 @@ +package team9.baseball.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.SignatureException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import team9.baseball.domain.enums.ResourceServer; + +import java.util.HashMap; +import java.util.Map; + +class JwtUtilTest { + @Test + @DisplayName("발급 및 인증 테스트") + void createParseTest() { + Map map = new HashMap<>(); + map.put("userId", 1l); + map.put("userEmail", "isaac@naver.com"); + map.put("resourceServer", ResourceServer.GITHUB); + String jwt = JwtUtil.createToken("access", "user", map, 10); + Claims claims = JwtUtil.getTokenData(jwt); + + Assertions.assertThat(claims.get("userId", Integer.class)).isEqualTo(1); + Assertions.assertThat(claims.get("userEmail", String.class)).isEqualTo("isaac@naver.com"); + Assertions.assertThat(claims.get("resourceServer", String.class)).isEqualTo("GITHUB"); + } + + @Test + @DisplayName("서명 변조 테스트") + void signatureTest() { + Map map = new HashMap<>(); + map.put("userId", 1l); + map.put("userEmail", "isaac@naver.com"); + map.put("resourceServer", "GITHUB"); + String jwt = JwtUtil.createToken("access", "user", map, 10); + String wrongJwt = jwt.substring(0, jwt.length() - 1); + + checkWriteSignature(jwt, true); + checkWriteSignature(wrongJwt, false); + } + + void checkWriteSignature(String jwt, boolean validated) { + boolean isWriteSignature = true; + try { + Claims claims = JwtUtil.getTokenData(jwt); + } catch (SignatureException ex) { + isWriteSignature = false; + } + Assertions.assertThat(isWriteSignature).isEqualTo(validated); + } +} diff --git a/docs/database/Schema.mwb b/docs/database/Schema.mwb index 2acb540db..568be67ae 100644 Binary files a/docs/database/Schema.mwb and b/docs/database/Schema.mwb differ diff --git a/docs/database/Schema.png b/docs/database/Schema.png index 6e8741e20..23e3032d3 100644 Binary files a/docs/database/Schema.png and b/docs/database/Schema.png differ