Skip to content

Commit

Permalink
Merge pull request #7 from Lunawood/master
Browse files Browse the repository at this point in the history
fix: JWT 토큰 추가 및 에러 핸들러 추가
  • Loading branch information
Lunawood authored Nov 27, 2024
2 parents 3b1c4a0 + 3d0b579 commit 086481c
Show file tree
Hide file tree
Showing 27 changed files with 651 additions and 378 deletions.
7 changes: 3 additions & 4 deletions docker-test-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.auth0:java-jwt:3.18.1'
implementation 'io.github.cdimascio:java-dotenv:+'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'org.json:json:20171018'
implementation 'org.json:json:20171018'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.springfox:springfox-boot-starter:3.0.0'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.h2database:h2'
Expand Down
5 changes: 5 additions & 0 deletions docker-test-server/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

docker-compose down
docker rmi docker-test-server-server:latest
docker-compose up -d
14 changes: 5 additions & 9 deletions docker-test-server/sql/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ CREATE DATABASE IF NOT EXISTS toothFairy;
USE toothFairy;

CREATE TABLE user_tb (
email VARCHAR(255) PRIMARY KEY,
pet_name VARCHAR(50),
pet_weight INT,
access_token VARCHAR(255),
refresh_token VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO user_tb (email, pet_name, pet_weight, access_token, refresh_token)
VALUES ('[email protected]', 'Charlie', 12, 'test_access_token', 'test_refresh_token');
id BIGINT PRIMARY KEY,
pet_name VARCHAR(50) NOT NULL,
pet_weight INT NOT NULL,
random_id VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.server.error.CErrorResponse;
import com.example.server.error.CException;
import com.example.server.error.ErrorCode;
import com.example.server.jwt.JwtTokenService;
import com.example.server.model.User;
import com.example.server.model.UserPet;
import com.example.server.service.HomeService;
Expand All @@ -22,7 +25,7 @@
public class HomeController {
private final InvalidTokenService invalidTokenService;
private final HomeService homeService;

private final JwtTokenService jwtTokenService;
// 홈페이지
// 펫이름 가져오기
// Input : 파라미터 AccessToken
Expand All @@ -32,23 +35,46 @@ public class HomeController {
// Output(500) : 서버에러
@GetMapping("")
public ResponseEntity<?> home(@RequestHeader("AccessToken") String AccessToken) {
// 1. RefreshToken Valid?
try {
if(jwtTokenService.validateAccessToken(AccessToken) == false) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

} catch (Exception e) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

// 2. Check userId
Long userId;
try {
userId = jwtTokenService.extractIdFromAccessToken(AccessToken);
if(invalidTokenService.existsById(userId) == false) {
throw new CException(ErrorCode.INVALID_TOKEN);
}
} catch (Exception e) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

// 3. Get Pet Information By UserId
User user = null;
try {
invalidTokenService.isTokenInvalid(AccessToken);
System.out.println(AccessToken);
user = homeService.getPetInformation(AccessToken);
} catch (CException e) {
return ResponseEntity.status(e.getErrorCode().getStatus()).body(e.getErrorCode().getMessage());
user = homeService.getPetInformation(userId);
} catch (Exception e) {
return ResponseEntity.status(500).body(e.toString());
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

if(Objects.isNull(user))
return ResponseEntity.status(500).body("유저 정보를 가져오지 못 했습니다.");
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);

UserPet userPet = new UserPet(user.getPetName(), user.getPetWeight());

// access 토큰 확인
return ResponseEntity.status(200).body(userPet);
return ResponseEntity
.status(ErrorCode.SUCCESS.getStatus())
.body(CErrorResponse.builder()
.status(ErrorCode.SUCCESS.getStatus())
.message(userPet)
.build()
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.server.error.CErrorResponse;
import com.example.server.error.CException;
import com.example.server.error.ErrorCode;
import com.example.server.jwt.JwtTokenService;
import com.example.server.model.Token;
import com.example.server.service.LoginService;

import lombok.RequiredArgsConstructor;
Expand All @@ -16,27 +20,60 @@
@RequestMapping("/api/login")
public class LoginController {
private final LoginService loginService;
private final JwtTokenService jwtTokenService;

// 로그인시 재가입 여부를 판단하는 API
// Input : AccessToken, RefreshToken, ExpiresIn, RefreshTokenExpiresIn
// Output(403) : 이미 가입한 사용자 -> 데이터 Update -> 홈페이지
// Output(401) : 유효하지 않은 토큰
// Output(200) : 재가입 No -> 회원가입 페이지
// 로그인시 회원가입, 재로그인을 판단하는 API
// Input : AccessToken(Kakao Access Token)
// Output(403) : 존재하지 않는 유저(회원가입) : Message "존재하지 않는 유저"
// Output(401) : 유효하지 않은 토큰(에러) : Message "유효하지 않은 '소셜' 액세스 토큰"
// Output(200) : 가입 유저(재로그인)
@GetMapping("")
public ResponseEntity<?> login(
@RequestHeader("AccessToken") String AccessToken,
@RequestHeader("ExpiresIn") Integer ExpiresIn,
@RequestHeader("RefreshToken") String RefreshToken,
@RequestHeader("RefreshTokenExpiresIn") Integer RefreshTokenExpiresIn
){
public ResponseEntity<?> login (@RequestHeader("AccessToken") String AccessToken) {
Long userId = Long.valueOf(0);
// 1. KAKAO OPEN API로 userId 확인.
// => Error : 유효하지 않는 토큰(에러)
try {
loginService.isUserRejoin(AccessToken, ExpiresIn, RefreshToken, RefreshTokenExpiresIn);
} catch (CException e) {
return ResponseEntity.status(e.getErrorCode().getStatus()).body(e.getErrorCode().getMessage());
} catch (Exception e) {
return ResponseEntity.status(500).body(e.toString());
userId = loginService.getUserIdByKakaoOpenApi(AccessToken);
} catch(Exception e) {
throw new CException(ErrorCode.KAKAO_INVALID_TOKEN);
}

return ResponseEntity.status(200).body(".");
// 2. userId로 데이터베이스 확인
// => 데이터베이스 false : 존재하지 않는 유저(회원가입)
try {
System.out.println(loginService.getUserIdByDatabase(userId));
if(loginService.getUserIdByDatabase(userId) == false) {
throw new CException(ErrorCode.GHOST_USER);
}
} catch(Exception e) {
System.out.println("Here");
throw new CException(ErrorCode.GHOST_USER);
}

// 3. JWT AccessToken, RefreshToken 토큰 발급
Token token = new Token();
String randomId = jwtTokenService.generateRandomId();
try {
String accessToken = jwtTokenService.createAccessToken(userId);
String refreshToken = jwtTokenService.createRefreshToken(userId, randomId);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
} catch(Exception e) {
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

// 4. RefreshToken RandomID Database에 저장
try {
loginService.updateRandomIdByUserId(userId, randomId);
} catch(Exception e) {
throw new CException(ErrorCode.KAKAO_INVALID_TOKEN);
}

return ResponseEntity
.status(ErrorCode.SUCCESS.getStatus())
.body(CErrorResponse.builder()
.status(ErrorCode.SUCCESS.getStatus())
.message(token)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.example.server.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.server.error.CErrorResponse;
import com.example.server.error.CException;
import com.example.server.error.ErrorCode;
import com.example.server.jwt.JwtTokenService;
import com.example.server.model.Token;
import com.example.server.service.RefreshTokenService;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/refresh")
public class RefreshTokenController {
private final RefreshTokenService refreshTokenService;
private final JwtTokenService jwtTokenService;

// RefreshToken으로 AccessToken과 RefreshToken 새로 발급
@GetMapping("")
public ResponseEntity<?> refreshAccessToken (@RequestHeader("RefreshToken") String RefreshToken) {
// 1. RefreshToken Valid?
try {
if(jwtTokenService.validateRefreshToken(RefreshToken) == false) {
throw new CException(ErrorCode.INVALID_TOKEN);
}
} catch (Exception e) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

// 2. Get userId & radomId from RefreshToken
Long userId = null;
String randomId = null;
try {
userId = jwtTokenService.extractIdFromRefreshToken(RefreshToken);
randomId = jwtTokenService.extractRandomIdFromRefreshToken(RefreshToken);
} catch (Exception e) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

if(userId == null || randomId == null)
throw new CException(ErrorCode.INVALID_TOKEN);

// 3. Check userId & radomId
try {
if(refreshTokenService.checkIdAndRandomId(userId, randomId) == false) {
throw new CException(ErrorCode.INVALID_TOKEN);
}
} catch (Exception e) {
throw new CException(ErrorCode.INVALID_TOKEN);
}

// 4. JWT AccessToken, RefreshToken 토큰 발급
Token token = new Token();
String new_randomId = jwtTokenService.generateRandomId();
try {
String accessToken = jwtTokenService.createAccessToken(userId);
String refreshToken = jwtTokenService.createRefreshToken(userId, new_randomId);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
} catch(Exception e) {
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

// 5. RefreshToken RandomID Database에 수정
try {
refreshTokenService.updateRandomIdByUserId(userId, new_randomId);
} catch(Exception e) {
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

return ResponseEntity
.status(ErrorCode.SUCCESS.getStatus())
.body(CErrorResponse.builder()
.status(ErrorCode.SUCCESS.getStatus())
.message(token)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package com.example.server.controller;

import javax.validation.Valid;

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.RequestHeader;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.server.error.CErrorResponse;
import com.example.server.error.CException;
import com.example.server.error.ErrorCode;
import com.example.server.jwt.JwtTokenService;
import com.example.server.model.Token;
import com.example.server.model.UserPet;
import com.example.server.service.RegisterService;

import lombok.RequiredArgsConstructor;
Expand All @@ -16,27 +25,60 @@
@RequestMapping("/api/register")
public class RegisterController {
private final RegisterService registerService;
private final JwtTokenService jwtTokenService;

// 회원가입이 끝나면 저장하기 위한 API
// Input : AccessToken, ExpiresI, RefreshToken, RefreshTokenExpiresIn, PetName, PetWeight
// Input : AccessToken, PetName, PetWeight
// Output : status(200, 401, 500)
@PostMapping("")
public ResponseEntity<?> register(
@RequestHeader("AccessToken") String AccessToken,
@RequestHeader("ExpiresIn") Integer ExpiresIn,
@RequestHeader("RefreshToken") String RefreshToken,
@RequestHeader("RefreshTokenExpiresIn") Integer RefreshTokenExpiresIn,
@RequestHeader("PetName") String PetName,
@RequestHeader("PetWeight") Integer PetWeight
@Valid @RequestBody UserPet userPet
) {
Long userId = Long.valueOf(0);
// 1. KAKAO OPEN API로 userId 확인.
// => Error : 유효하지 않는 토큰(에러)
try {
userId = registerService.getUserIdByKakaoOpenApi(AccessToken);
} catch(Exception e) {
throw new CException(ErrorCode.KAKAO_INVALID_TOKEN);
}

// 2. userId로 데이터베이스 확인
// => 데이터베이스 true : 이미 존재하는 유저(Bad Request 400)
try {
if(registerService.getUserIdByDatabase(userId) == true) {
throw new CException(ErrorCode.REGISTERED_USER);
}
} catch(Exception e) {
throw new CException(ErrorCode.REGISTERED_USER);
}

// 3. JWT AccessToken, RefreshToken 토큰 발급
Token token = new Token();
String randomId = jwtTokenService.generateRandomId();
try {
String accessToken = jwtTokenService.createAccessToken(userId);
String refreshToken = jwtTokenService.createRefreshToken(userId, randomId);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
} catch(Exception e) {
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

// 4. 모든 정보 저장.
try {
registerService.isUserRejoin(AccessToken, ExpiresIn, RefreshToken, RefreshTokenExpiresIn, PetName, PetWeight);
} catch (CException e) {
return ResponseEntity.status(e.getErrorCode().getStatus()).body(e.getErrorCode().getMessage());
} catch (Exception e) {
return ResponseEntity.status(500).body(e.toString());
registerService.createUserInfo(userId, randomId, userPet.getPetName(), userPet.getPetWeight());;
} catch(Exception e) {
throw new CException(ErrorCode.INTERNAL_SERVER_ERROR);
}

return ResponseEntity.status(200).body(".");
return ResponseEntity
.status(ErrorCode.SUCCESS.getStatus())
.body(CErrorResponse.builder()
.status(ErrorCode.SUCCESS.getStatus())
.message(token)
.build()
);
}
}
Loading

0 comments on commit 086481c

Please sign in to comment.