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

fix: JWT 토큰 추가 및 에러 핸들러 추가 #7

Merged
merged 1 commit into from
Nov 27, 2024
Merged
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
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
Loading