Skip to content

Commit

Permalink
id으로도 사용자 정보 조회할 수 있도록 API 수정, 문서 보강 (#33)
Browse files Browse the repository at this point in the history
* doc: 문서에 기본적으로 들어가야하는 내용 추가

* up: 프로그램 버전 업

* add: 개발된 웹 서버를 바로 실행할 수 있는 docker-compose 추가

* up: 개발된 제품을 쉽게 실행할 수 있도록 수정

* fix: 경로 수정

* doc: 환경 변수 기본 표 추가

* up: id 혹은 이메일로 사용자 조회할 수 있도록 수정

* doc: 문서 보강

* add: 기본 적인 key 추가

* up: 문서 보강

* up: 누락된 설명 추가

* up: 카카오 로그인을 위한 웹앱 수정

* add: 환경 변수 범례 소개
  • Loading branch information
parkgang committed Dec 24, 2021
1 parent c7dfb44 commit b0fcb22
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 51 deletions.
60 changes: 43 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
# oauth-server
# Overview

OAuth 인증서버를 구축합니다.

1. JWT Token을 사용합니다.
1. 해당 앱을 사용하기 위해서는 kakao 개발자 센터에서 애플리케이션을 발급받아야 합니다. 발급 후 설정방법은 노션을 참고하도록 합니다.
1. API Spec는 `{url}/swagger/index.html` 으로 요청하여`swagger` 를 참고해주세요.
1. JWT Token을 기반으로 구축 된 인증서버 입니다.
1. API Spec는 `{url}/swagger/index.html``swagger` 를 참고해주세요.
1. kakao login의 경우 콜백 때문에 테스트를 위해서는 웹앱이 필요합니다. [test/kakao-login](./test/kakao-login) 를 참고해주세요.
1. 환경 변수 범례의 경우 [configs](./configs) 를 참고해주세요.
1. 이외 컨테이너 실행과 같은 명령어는 vscode `tasks.json` 에도 정의되어 있음으로 편하게 task로 실행하세요! 😎

# Stack

1. go:1.16.3
1. vscode
1. gin
1. redis:6.2.4
1. mysql:5.7.16
1. docker

# 시퀀스 다이어그램
# Quick Start

## 개발 환경

1. [config.dev.json](./configs/config.dev.json) 에서 필요한 환경 변수를 설정합니다.
1. `docker-compose up -d` 으로 db server와 같은 개발에 필요한 컨테이너를 올립니다.
1. vscode의 `실행 및 디버그` 에서 `Server` 으로 실행 혹은 터미널에 `export GO_ENV=development && go run main.go` 으로 시작합니다.

## 제품 시작

1. [config.prod.json](./configs/config.prod.json) 에서 필요한 환경 변수를 설정합니다.
1. `docker-compose up -d` docker image 생성 후 컨테이너를 실행합니다.

> db 서비스가 초기화 후 웹 서버가 실행되어야하는데 `scratch` image라 [wait-for-it.sh](https://github.com/vishnubob/wait-for-it/) 를 실행하기가 쉽지 않았습니다. 근본적인 해결방법은 아니지만 local 환경에서 build 된 Dockerfile를 테스트하고 싶은 경우 재시작으로 해결하도록 합니다. local 이외 Prod는 k8s가 알아서 핸들링하기 때문에 상관이 없습니다.
# Other

## swagger 확인 및 업데이트 방법

swagger가 탑재되어 있음으로 [http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html) 에서 swagger 문서를 확인하실 수 있습니다.
이외, swagger 업데이트 명령어가 vscode `tasks.json` 에 정의되어 있음으로 편하게 task로 실행하세요! 😎

> 추가 예정
> 직접 명령어로 수행 하시려면 아래의 명령어를 사용하세요.
>
> ```shell
> export PATH=$(go env GOPATH)/bin:$PATH
> swag i
> ```
# 빠른 시작
## live reloading로 실행 방법

1. vscode에서 `F5` 혹은 `go run main.go` 으로 시작합니다.
1. kakao developers에서 kakao application 정보 및 환경변수를 입력해주세요.
javascript, python, ruby와 같은 인터프리터 언어로 작업하는 데 익숙한 사람들은 특히 golang에서 live reloading을 갈망합니다.
이를 위해 `tasks.json` 에 live reloading를 위한 명령어를 정의해 놓았습니다. 해당 기능을 사용하기 위해서 아래의 패키지를 다운받아주세요.

# 의존성
```shell
go get github.com/codegangsta/gin
```

| 이름 | 명령어 |
| ------------------------------------------ | ------------------------------------- |
| [gin](https://github.com/gin-gonic/gin) | `go get -u github.com/gin-gonic/gin` |
| [redis](https://github.com/go-redis/redis) | `go get github.com/go-redis/redis/v8` |
| [GORM](https://github.com/go-gorm/gorm) | `go get -u gorm.io/gorm` |
> live reloading로 속도감 있게 개발하는 것은 좋지만 아쉬운 점은 breakpoint을 사용하기 위해서는 필요할 때마다 process를 attach 해줘야합니다.
> 이유는 reloading 마다 빌드되어 실행되는 파일이 달라서 process를 계속 추적할 수 없습니다. (아직 debug tool에서 지원되지 않는 것으로 확인됨)
> 만약, live reloading에서 breakpoint가 필요하면 `launch.json` 에 정의된 `Attach to Gin` 를 실행하세요! (이럴 줄 알고 미리 정의해 놓았습니다)
> reloading시 연결이 끈어지지만 빠른 개발을 위해 live reloading 진행하다가 필요할 떄 debug를 해나가는 전략이 더 유리할 것으로 기대합니다.
40 changes: 40 additions & 0 deletions configs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 환경 변수

1. 모두 대문자 key인 환경변수의 경우 `config` 에도 지정되어 있고 `env` 로도 넣어준다면 `env` 값을 사용하게 됩니다.
1. kakao 로그인을 사용하기 위해서는 [kakao developers](https://developers.kakao.com/) 에서 kakao application 정보를 입력해야합니다.
1. JWT 관련 secret 유출 시 token을 복호화 할 수 있음으로 주의하도록 합니다!

## 표 범례

| 구성 요소 | 설명 |
| ------------- | ----------------------------------------------------------------------------- |
| Variable | 환경 변수 이름 |
| dev | 환경 변수가 개발 환경에서 사용되는지 여부 |
| qa/prod | 환경 변수가 qa, production 환경에서 사용되는지 여부 |
| Default value | 시스템 환경 변수를 사용해 환경 변수를 정하지 않았을 때 기본적으로 적용되는 값 |
| Example | 환경 변수 값으로 들어갈 수 있는 예시의 나열 |
| Explanation | 환경 변수에 대한 설명 |

##

| Variable | dev | qa/prod | Default value | Example | Explanation |
| --------------------- | :-: | :-----: | :-----------: | ----------------------- | ------------------------------------------------------------------------------------- |
| GO_ENV ||| 🤷‍♂️ | development, production | `Go 실행 환경` 을 설정하는 값이며 프로그램 시작 전 값이 있어야 합니다. |
| STAGES ||| 🤷‍♂️ | local, qa, prod | 배포 환경을 구분하기 위함이며, 해당 값에 따라서 swagger 문서가 달라집니다. |
| SERVER_PORT ||| 🤷‍♂️ | 8080 | 웹 서버 HTTP listen port 입니다. |
| SWAGGER_HOSTNAME ||| 🤷‍♂️ | localhost, test.com | CORS 문제를 해결하기 위하여 사용되며 swagger 문서를 요청하는 도메인네임 입니다. |
| SWAGGER_PORT ||| 🤷‍♂️ | 8080, 443 | swagger 문서를 요청하는 port 입니다. |
| KAKAO_REST_API_KEY ||| 🤷‍♂️ | | kakao 로그인 API를 사용하기 위해서 필요하며 kakao API Key 입니다. |
| KAKAO_REDIRECT_URI ||| 🤷‍♂️ | | kakao 로그인 API를 사용하기 위해서 필요하며 kakao 로그인 완료 시 콜백되는 주소입니다. |
| JWT_ACCESS_SECRET ||| 🤷‍♂️ | | jwt access token 발급 시 암호화 하기 위한 key 입니다. |
| JWT_REFRESH_SECRET ||| 🤷‍♂️ | | jwt refresh token 발급 시 암호화 하기 위한 key 입니다. |
| AUTH_REDIRECT_URL ||| 🤷‍♂️ | | kakao 로그인 완료 시 서명된 jwt token을 queryString으로 전달 할 웹앱 주소입니다. |
| MYSQL_MASTER_HOST ||| 🤷‍♂️ | | `DB 주소``MASTER 환경` 에서 사용되는 값입니다. |
| MYSQL_MASTER_PORT ||| 🤷‍♂️ | | `DB port``MASTER 환경` 에서 사용되는 값입니다. |
| MYSQL_MASTER_USERNAME ||| 🤷‍♂️ | | `DB 계정명` 으로 `MASTER 환경` 에서 사용되는 값입니다. |
| MYSQL_MASTER_PASSWORD ||| 🤷‍♂️ | | `DB 계정의 비밀번호``MASTER 환경` 에서 사용되는 값입니다. |
| MYSQL_MASTER_DATABASE ||| 🤷‍♂️ | | `DB명` 으로 `MASTER 환경` 에서 사용되는 값입니다. |
| REDIS_HOST ||| 🤷‍♂️ | | `DB 주소` 입니다. |
| REDIS_PORT ||| 🤷‍♂️ | | `DB port` 입니다. |
| REDIS_PASSWORD ||| 🤷‍♂️ | | `DB 계정의 비밀번호` 입니다. |
| REDIS_DATABASE ||| 🤷‍♂️ | | `DB명` 입니다. |
2 changes: 1 addition & 1 deletion configs/config.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"SWAGGER_PORT": "8080",

"KAKAO_REST_API_KEY": "",
"KAKAO_REDIRECT_URI": "",
"KAKAO_REDIRECT_URI": "http://localhost:8080/api/users/login/kakao",

"JWT_ACCESS_SECRET": "",
"JWT_REFRESH_SECRET": "",
Expand Down
4 changes: 2 additions & 2 deletions configs/config.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
"SWAGGER_PORT": "8080",

"KAKAO_REST_API_KEY": "",
"KAKAO_REDIRECT_URI": "",
"KAKAO_REDIRECT_URI": "http://localhost:3001/api/users/login/kakao",

"JWT_ACCESS_SECRET": "",
"JWT_REFRESH_SECRET": "",
"AUTH_REDIRECT_URL": "http://localhost:3000/auth-end",
"AUTH_REDIRECT_URL": "http://localhost:2999/auth-end",

"MYSQL_MASTER_HOST": "host.docker.internal",
"MYSQL_MASTER_PORT": "3306",
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@ services:
container_name: redis-dev
ports:
- 6379:6379
oauth-server:
build:
context: ./
dockerfile: Dockerfile
image: belf-oauth-server
depends_on:
- mysql
container_name: oauth-server-dev
ports:
- 3001:8080
environment:
- SWAGGER_PORT=3001
10 changes: 5 additions & 5 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,9 @@ var doc = `{
}
}
},
"/users/{userEmail}": {
"/users/{userKey}": {
"get": {
"description": "사용자 이메일으로 사용자를 조회합니다.",
"description": "id 혹은 email로 사용자를 조회합니다.",
"consumes": [
"application/json"
],
Expand All @@ -560,12 +560,12 @@ var doc = `{
"tags": [
"User"
],
"summary": "Email로 사용자 정보 조회",
"summary": "사용자 조회",
"parameters": [
{
"type": "string",
"description": "사용자 Email",
"name": "userEmail",
"description": "id or email",
"name": "userKey",
"in": "path",
"required": true
}
Expand Down
10 changes: 5 additions & 5 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,9 +528,9 @@
}
}
},
"/users/{userEmail}": {
"/users/{userKey}": {
"get": {
"description": "사용자 이메일으로 사용자를 조회합니다.",
"description": "id 혹은 email로 사용자를 조회합니다.",
"consumes": [
"application/json"
],
Expand All @@ -540,12 +540,12 @@
"tags": [
"User"
],
"summary": "Email로 사용자 정보 조회",
"summary": "사용자 조회",
"parameters": [
{
"type": "string",
"description": "사용자 Email",
"name": "userEmail",
"description": "id or email",
"name": "userKey",
"in": "path",
"required": true
}
Expand Down
10 changes: 5 additions & 5 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,15 @@ paths:
summary: 사용자 생성 (회원가입)
tags:
- User
/users/{userEmail}:
/users/{userKey}:
get:
consumes:
- application/json
description: 사용자 이메일으로 사용자를 조회합니다.
description: id 혹은 email로 사용자를 조회합니다.
parameters:
- description: 사용자 Email
- description: id or email
in: path
name: userEmail
name: userKey
required: true
type: string
produces:
Expand All @@ -285,7 +285,7 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/models.ErrResponse'
summary: Email로 사용자 정보 조회
summary: 사용자 조회
tags:
- User
/users/avatar:
Expand Down
21 changes: 11 additions & 10 deletions internal/app/handlers/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,32 +291,33 @@ func UserInfoTokenQuey(c *gin.Context) {
c.JSON(http.StatusOK, res)
}

// @Summary Email로 사용자 정보 조회
// @Description 사용자 이메일으로 사용자를 조회합니다.
// @Summary 사용자 조회
// @Description id 혹은 email로 사용자를 조회합니다.
// @Tags User
// @Accept json
// @Produce json
// @Param userEmail path string true "사용자 Email"
// @Param userKey path string true "id or email"
// @Success 200 {object} models.UserInfo
// @Failure 400
// @Failure 404
// @Failure 500 {object} models.ErrResponse
// @Router /users/{userEmail} [get]
func UserInfoEmailQuey(c *gin.Context) {
userEmail := c.Param("userEmail")
if userEmail == "" {
// @Router /users/{userKey} [get]
func UserInfoQuey(c *gin.Context) {
userKey := c.Param("userKey")
if userKey == "" {
c.Status(http.StatusBadRequest)
return
}

user := entitys.User{}
if err := orm.Client.Where("email = ?", userEmail).Find(&user).Error; err != nil {
result := orm.Client.Where("id = ?", userKey).Or("email = ?", userKey).Find(&user)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, models.ErrResponse{
Message: err.Error(),
Message: result.Error.Error(),
})
return
}
if userEmail != user.Email {
if result.RowsAffected == 0 {
c.Status(http.StatusNotFound)
return
}
Expand Down
2 changes: 1 addition & 1 deletion internal/app/routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func Use(api *gin.RouterGroup) {
users.POST("", handlers.UserSignup)
users.GET("", middlewares.TokenAuthMiddleware(), handlers.UserInfoTokenQuey)
users.DELETE("", middlewares.TokenAuthMiddleware(), handlers.UserWithdrawal)
users.GET(":userEmail", handlers.UserInfoEmailQuey)
users.GET(":userKey", handlers.UserInfoQuey)
users.POST("/avatar", middlewares.TokenAuthMiddleware(), handlers.UploadAvatar)
users.DELETE("/avatar", middlewares.TokenAuthMiddleware(), handlers.DeleteAvatar)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package project

const (
AppName string = "oauth-server"
AppVersion string = "1.1.0"
AppVersion string = "1.2.0"
)
6 changes: 4 additions & 2 deletions test/kakao-login/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Overview

1. kakao login 기능을 테스트 하기 위한 웹앱 입니다.
1. `node@14` 의 CRA project 입니다.
1. `node@v14.16.1` 의 CRA project 입니다.

# Quick start

1. 아래의 명령어로 웹 앱을 실행해주세요.
> 사전에 인증 서버가 실행 중 이여야 합니다.
1. 해당 디렉터리에서 아래의 명령어로 웹 앱을 실행해주세요.

```shell
npm i && npm run start
Expand Down
14 changes: 12 additions & 2 deletions test/kakao-login/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
delLocalStorageAccessToken,
delLocalStorageRefreshToken,
getLocalStorageAccessToken,
getLocalStorageRefreshToken,
} from "libs/local-storage";
import { useEffect, useState } from "react";

Expand All @@ -14,6 +15,7 @@ export default function Home() {
const [error, setError] = useState<any>();

const accessToken = getLocalStorageAccessToken();
const refreshToken = getLocalStorageRefreshToken();

function handleKakaoLogin() {
if (!config) {
Expand All @@ -25,10 +27,16 @@ export default function Home() {
window.location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code`;
}

function handleTokenClear() {
delLocalStorageAccessToken();
delLocalStorageRefreshToken();
window.location.reload();
}

useEffect(() => {
(async () => {
try {
if (!accessToken) {
if (!accessToken && !refreshToken) {
return;
}
const res = await GetUserInfo();
Expand Down Expand Up @@ -72,6 +80,7 @@ export default function Home() {
if (error) {
return (
<>
<button onClick={handleTokenClear}>token clear</button>
<h3>에러 발생</h3>
<span>{error}</span>
</>
Expand All @@ -88,9 +97,9 @@ export default function Home() {

return (
<>
<button onClick={handleKakaoLogin}>카카오 로그인</button>
{userInfo ? (
<>
<button onClick={handleTokenClear}>로그아웃</button>
<h3>카카오 로그인 완료</h3>
<img
src={userInfo.avatarImage}
Expand All @@ -103,6 +112,7 @@ export default function Home() {
</>
) : (
<>
<button onClick={handleKakaoLogin}>카카오 로그인</button>
<h3>로그인되지 않았거나 token이 손상됨</h3>
</>
)}
Expand Down

0 comments on commit b0fcb22

Please sign in to comment.