지난 몇 달 동안 저는 Go 프로그래밍 언어를 사용하여 GraphQL 시리즈를 작성해 왔습니다. 먼저 다음과 같은 방법을 살펴봤습니다. GraphQL 및 Go 시작하기를 클릭한 다음 리졸버를 사용하여 데이터 관계 처리 를 GraphQL 객체에 포함할 수 있습니다. 한 단계 더 나아가서 다음을 포함하는 방법을 살펴보았습니다. 권한 부여를 위한 JSON 웹 토큰(JWT) 를 GraphQL 객체에서 사용할 수 있지만 데이터베이스는 없습니다.
이 GraphQL과 Golang 여정의 논리적 다음 단계는 다음을 연결하는 것입니다. 카우치베이스 를 JSON 웹 토큰(JWT)을 통한 인증이 포함된 완전한 기능의 GraphQL 기반 API로 전환합니다. 다음을 통해 계정 생성, JWT 유효성 검사 및 라이브 데이터 작업을 처리하는 방법을 살펴보겠습니다. GraphQL 쿼리.
디자인 및 개발을 시작하기 전에 이 주제에 대한 이전 튜토리얼을 보지 않았다면 꼭 봐야 할 것입니다. 다음과 같은 사용법을 이해할 때까지는 JWT 측면에 대해 알아보지 않는 것이 좋습니다. GraphQL Golang으로.
JWT 애플리케이션을 사용하여 GraphQL에 Couchbase 포함하기
GraphQL 기반 애플리케이션을 만드는 과정을 반복하는 대신 시리즈에서 중단한 부분부터 다시 시작하겠습니다. 먼저 이전 JWT 튜토리얼 시리즈에서 다음과 같은 코드가 남았습니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
패키지 메인 가져오기 ( "context" "encoding/json" "오류" "fmt" "net/http" jwt "github.com/dgrijalva/jwt-go" "github.com/graphql-go/graphql" "github.com/mitchellh/mapstructure" ) 유형 사용자 구조체 { Id 문자열 `json:"id"` 사용자 이름 문자열 `json:"username"` 비밀번호 문자열 `json:"비밀번호"` } 유형 블로그 구조체 { Id 문자열 `json:"id"` 제목 문자열 `json:"title"` 콘텐츠 문자열 `json:"content"` 작성자 문자열 `json:"author"` 페이지 조회수 int32 `json:"페이지뷰"` } var jwtSecret []바이트 = []바이트("thepolyglotdeveloper") var accountsMock []사용자 = []사용자{ 사용자{ Id: "1", 사용자 이름: "nraboy", 비밀번호: "1234", }, 사용자{ Id: "2", 사용자 이름: "mraboy", 비밀번호: "5678", }, } var 블로그 모의 []블로그 = []블로그{ 블로그{ Id: "1", 작성자: "nraboy", 제목: "샘플 기사", 콘텐츠: "닉 라보이가 작성한 샘플 글입니다.", 페이지 조회수: 1000, }, } var 계정 유형 *그래프 쿼리.개체 = 그래프 쿼리.새 개체(그래프 쿼리.ObjectConfig{ 이름: "계정", 필드: 그래프 쿼리.필드{ "id": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "username": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "비밀번호": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, }, }) var 블로그 유형 *그래프 쿼리.개체 = 그래프 쿼리.새 개체(그래프 쿼리.ObjectConfig{ 이름: "블로그", 필드: 그래프 쿼리.필드{ "id": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "title": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "content": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "author": &그래프 쿼리.필드{ 유형: 그래프 쿼리.문자열, }, "페이지뷰": &그래프 쿼리.필드{ 유형: 그래프 쿼리.Int, 해결: func(매개변수 그래프 쿼리.ResolveParams) (인터페이스{}, 오류) { _, err := ValidateJWT(매개변수.컨텍스트.가치("토큰").(문자열)) 만약 err != nil { 반환 nil, err } 반환 매개변수.출처.(블로그).페이지 조회수, nil }, }, }, }) func ValidateJWT(t 문자열) (인터페이스{}, 오류) { 만약 t == "" { 반환 nil, 오류.신규("인증 토큰이 있어야 합니다") } 토큰, _ := jwt.Parse(t, func(토큰 *jwt.토큰) (인터페이스{}, 오류) { 만약 _, 확인 := 토큰.방법.(*jwt.SigningMethodHMAC); !확인 { 반환 nil, fmt.오류f("오류가 발생했습니다.") } 반환 jwtSecret, nil }) 만약 클레임, 확인 := 토큰.클레임.(jwt.MapClaims); 확인 && 토큰.유효 { var decodedToken 인터페이스{} 지도 구조.디코딩(클레임, &decodedToken) 반환 decodedToken, nil } else { 반환 nil, 오류.신규("잘못된 인증 토큰") } } func 토큰 엔드포인트 생성(응답 http.응답서 작성기, 요청 *http.요청) { var 사용자 사용자 _ = json.뉴디코더(요청.본문).디코딩(&사용자) 토큰 := jwt.새로운 클레임(jwt.서명 방법HS256, jwt.MapClaims{ "username": 사용자.사용자 이름, "비밀번호": 사용자.비밀번호, }) 토큰 문자열, 오류 := 토큰.서명된 문자열(jwtSecret) 만약 오류 != nil { fmt.Println(오류) } 응답.헤더().설정("콘텐츠 유형", "application/json") 응답.쓰기([]바이트(`{ "토큰": "` + 토큰 문자열 + `" }`)) } func 메인() { fmt.Println("응용 프로그램 시작::12345...") rootQuery := 그래프 쿼리.새 개체(그래프 쿼리.ObjectConfig{ 이름: "쿼리", 필드: 그래프 쿼리.필드{ "계정": &그래프 쿼리.필드{ 유형: 계정 유형, 해결: func(매개변수 그래프 쿼리.ResolveParams) (인터페이스{}, 오류) { 계정, err := ValidateJWT(매개변수.컨텍스트.가치("토큰").(문자열)) 만약 err != nil { 반환 nil, err } 에 대한 _, 계정 모의 := 범위 accountsMock { 만약 계정 모의.사용자 이름 == 계정.(사용자).사용자 이름 { 반환 계정 모의, nil } } 반환 &사용자{}, nil }, }, "블로그": &그래프 쿼리.필드{ 유형: 그래프 쿼리.뉴리스트(블로그 유형), 해결: func(매개변수 그래프 쿼리.ResolveParams) (인터페이스{}, 오류) { 반환 블로그 모의, nil }, }, }, }) 스키마, _ := 그래프 쿼리.새로운 스키마(그래프 쿼리.스키마 구성{ 쿼리: rootQuery, }) http.핸들펀크("/그래프QL", func(응답 http.응답서 작성기, 요청 *http.요청) { 결과 := 그래프 쿼리.Do(그래프 쿼리.매개변수{ 스키마: 스키마, 요청 문자열: 요청.URL.쿼리().Get("query"), 컨텍스트: 컨텍스트.WithValue(컨텍스트.배경(), "토큰", 요청.URL.쿼리().Get("토큰")), }) json.새 인코더(응답).인코딩(결과) }) http.핸들펀크("/로그인", 토큰 엔드포인트 생성) http.ListenAndServe(":12345", nil) } |
이제 우리의 목표는 모든 모의 데이터를 Couchbase에 존재하는 실제 데이터로 교체하는 것입니다. 이 튜토리얼에서는 블로그 데이터 생성에 대해서는 다루지 않겠지만, 변이에 대해 배우고 싶으시면 이전 튜토리얼 중 하나를 확인하시기 바랍니다.
동적 데이터를 사용하기 위한 첫 번째 단계는 데이터베이스인 Couchbase를 설정하는 것입니다. 각 GraphQL 객체에서 사용할 다음 전역 변수를 만듭니다:
1 |
var 버킷 *gocb.버킷 |
글로벌 버킷 참조가 생성되었으므로 이제 Couchbase 클러스터에 연결을 설정하고 버킷을 열어 보겠습니다. 이 작업은 프로젝트의 메인
함수입니다:
1 2 3 |
클러스터, _ := gocb.연결("couchbase://localhost") 클러스터.인증(gocb.비밀번호 인증기{사용자 이름: "예제", 비밀번호: "123456"}) 버킷, _ = 클러스터.OpenBucket("예제", "") |
위의 코드는 로컬에서 실행 중인 클러스터와 RBAC, 그리고 이미 생성 및 정의된 버킷 정보가 있다고 가정합니다. 이 애플리케이션에 대한 Couchbase 인스턴스를 제대로 구성하지 않은 경우 잠시 시간을 내어 구성하세요.
더 이상 모의 데이터를 사용하지 않고 NoSQL 데이터베이스로 작업하기 때문에 기본 Go 구조를 약간 변경해야 합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
유형 사용자 구조체 { Id 문자열 `json:"id,생략"` 사용자 이름 문자열 `json:"username"` 비밀번호 문자열 `json:"비밀번호"` 유형 문자열 `json:"type"` } 유형 블로그 구조체 { Id 문자열 `json:"id,생략"` 제목 문자열 `json:"title"` 콘텐츠 문자열 `json:"content"` 작성자 문자열 `json:"author"` 페이지 조회수 int32 `json:"페이지뷰"` 유형 문자열 `json:"type"` } |
추가하여 유형
속성을 사용하면 데이터를 차별화할 수 있으므로 더 나은 쿼리를 작성할 수 있습니다. Go 데이터 구조를 변경한다고 해서 GraphQL 객체를 업데이트해야 하는 것은 아닙니다. 우리가 반환할 것으로 예상하는 것과 작업할 것으로 예상하는 것은 다를 수 있습니다.
이전 예제에서는 전달된 정보로 JSON 웹 토큰을 생성했습니다. 실제로는 실제 계정 정보로 JWT를 생성하고 싶습니다. 이를 가능하게 하려면 계정 생성을 위한 엔드포인트를 만들어야 합니다:
1 2 3 4 5 6 7 8 9 10 |
func 계정 엔드포인트 생성(응답 http.응답서 작성기, 요청 *http.요청) { 응답.헤더().설정("콘텐츠 유형", "application/json") var 계정 사용자 json.뉴디코더(요청.본문).디코딩(&계정) 해시, _ := bcrypt.비밀번호로부터 생성([]바이트(계정.비밀번호), 10) 계정.비밀번호 = 문자열(해시) id, _ := uuid.NewV4() 버킷.삽입(id.문자열(), 계정, 0) 응답.쓰기([]바이트(`{ "id": "` + id.String() + `" }`)) } |
위의 함수는 사용자 아이디와 비밀번호를 받아 bcrypt로 비밀번호를 해시한 후 데이터베이스에 삽입합니다. 이 계정에 대해 데이터베이스를 쿼리하고 인증 수단으로 해시를 비밀번호와 비교할 것입니다. 이렇게 하려면 토큰 엔드포인트 생성
함수입니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func 토큰 엔드포인트 생성(응답 http.응답서 작성기, 요청 *http.요청) { 응답.헤더().설정("콘텐츠 유형", "application/json") var 사용자 사용자 _ = json.뉴디코더(요청.본문).디코딩(&사용자) 쿼리 := gocb.NewN1qlQuery("SELECT example.* FROM example WHERE type = 'account' AND username = $1") var 매개변수 []인터페이스{} 매개변수 = 추가(매개변수, 사용자.사용자 이름) 결과, _ := 버킷.ExecuteN1qlQuery(쿼리, 매개변수) var 계정 사용자 결과.하나(&계정) 만약 bcrypt.해시 및 비밀번호 비교([]바이트(계정.비밀번호), []바이트(사용자.비밀번호)) != nil { 응답.쓰기([]바이트(`{ "메시지": "잘못된 비밀번호" }`)) 반환 } 토큰 := jwt.새로운 클레임(jwt.서명 방법HS256, jwt.MapClaims{ "사용자 이름": 계정.사용자 이름, }) 토큰 문자열, 오류 := 토큰.서명된 문자열(jwtSecret) 만약 오류 != nil { fmt.Println(오류) } 응답.쓰기([]바이트(`{ "토큰": "` + 토큰 문자열 + `" }`)) } |
전달된 사용자 아이디와 비밀번호를 가져와서 JWT를 만드는 대신 데이터베이스 쿼리를 수행한다는 점에 유의하세요. 정보가 전달된 정보와 일치하지 않으면 오류를 반환하고, 그렇지 않으면 사용자 아이디를 기반으로 JWT를 계속 생성합니다.
계정을 생성하고 이로부터 JSON 웹 토큰을 생성하는 확실한 방법이 있다고 가정하면, 모의 데이터가 아닌 Couchbase를 사용하도록 GraphQL 객체를 변경할 수 있습니다.
내부 메인
함수에는 rootQuery
객체를 가진 블로그
쿼리뿐만 아니라 계정
쿼리. 우리는 블로그
쿼리를 먼저 입력하면 다음과 같이 표시됩니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
"블로그": &그래프 쿼리.필드{ 유형: 그래프 쿼리.뉴리스트(블로그 유형), 해결: func(매개변수 그래프 쿼리.ResolveParams) (인터페이스{}, 오류) { 쿼리 := gocb.NewN1qlQuery("SELECT example.* FROM example WHERE type = 'blog'") 결과, _ := 버킷.ExecuteN1qlQuery(쿼리, nil) var 결과 블로그 var 블로그 []블로그 에 대한 결과.다음(&결과) { 블로그 = 추가(블로그, 결과) } 반환 블로그, nil }, }, |
블로그 데이터의 모의 목록을 반환하는 대신 N1QL 쿼리를 수행하여 결과를 반환합니다. Go 데이터 구조는 GraphQL 객체에 매핑됩니다.
N1QL 쿼리를 통해 블로그 데이터를 반환하더라도 페이지 조회수
속성은 여전히 객체에 정의된 대로 JWT로 보호됩니다.
마지막 쿼리는 다음과 같습니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
"계정": &그래프 쿼리.필드{ 유형: 계정 유형, 해결: func(매개변수 그래프 쿼리.ResolveParams) (인터페이스{}, 오류) { 계정, err := ValidateJWT(매개변수.컨텍스트.가치("토큰").(문자열)) 만약 err != nil { 반환 nil, err } var 사용자 사용자 지도 구조.디코딩(계정, &사용자) 쿼리 := gocb.NewN1qlQuery("SELECT example.* FROM example WHERE type = 'account' AND username = $1") var n1qlParams []인터페이스{} n1qlParams = 추가(n1qlParams, 사용자.사용자 이름) 결과, _ := 버킷.ExecuteN1qlQuery(쿼리, n1qlParams) 결과.하나(&사용자) 반환 사용자, nil }, }, |
디코딩된 토큰 정보를 검색하여 N1QL 쿼리에서 매개변수로 사용하고 있음을 알 수 있습니다. 이렇게 하면 토큰 데이터 또는 현재 로그인한 사용자를 기반으로 특정 계정을 쿼리할 수 있습니다.
데이터베이스에 몇 가지 데이터를 생성하고 어떤 일이 발생하는지 확인해 보세요.
결론
Go가 포함된 GraphQL 시리즈를 다음과 같이 구성하여 마무리했습니다. 카우치베이스 를 추가했습니다. 실제로 Couchbase를 추가해도 JWT 예제에는 아무런 변화가 없었으며 단지 사용할 데이터 소스만 제공되었을 뿐입니다. 이 시리즈의 이전 튜토리얼을 자세히 살펴보면 데이터뿐만 아니라 쿼리, 변경 및 보호가 포함된 GraphQL에 대해 자세히 알아볼 수 있습니다. 프로덕션 지원 API에서 기대할 수 있는 모든 기능을 기존의 REST API 접근 방식이 아닌 GraphQL을 통해 제공합니다.