官方认证指导文档
人话说就是登陆注册用户认证的解决方案
Webhook
JWT
授权访问
Hasura 支持基于角色的授权,其中通过为每个角色、表和操作(插入、更新等)创建规则来完成访问控制。这些访问控制规则使用动态会话变量,这些变量从您的身份验证服务 随每个请求传递到 GraphQL 引擎。角色信息是从X-Hasura-Role
和X-Hasura-Allowed-Roles
会话变量推断出来的。身份验证服务可以根据要求传递其他会话变量。
JWT 实现步骤
第 1 步:配置重启 graphql-engine
为啥这么配看这个文档 https://hasura.io/docs/latest/graphql/core/auth/authentication/jwt.html
第 2 步 启动自己的 Auth 服务签发 token
官网有个 Flask 的例子,GraphQL API with Python & Flask: JWT authentication
可是我不太熟悉 python,后面想打包成 Docker 镜像估计得折腾一段时间,想了想还是用熟悉的 Golang 写一个,后续打镜像也不大,python 据说 pull 个原始镜像就有好几百M
花了两三个小时写了一个粗糙的版本,代码在文章最后,先直接上结果的例子
先看下 token 里数据是啥,这个工具不错 https://www.box3.cn/tools/jwt.html
这个 token 客户端拿到后要放到 Http Header 的 Authorization: Bearer
后
这里的用户是 member, 去 Hasura console 配置一下权限
如果想嵌套查询
用上面同样的方式配置一下 author 表的权限
再次请求
GO 源码
package main
import (
"crypto/hmac"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
"time"
)
const (
hmacKey = "kukuku"
hasuraURL = "http://localhost:8080/v1/graphql"
jwtKey = "LTExIDA8LTExIDA8LTExIDA8LTExIDA8"
hasuraAdminSecret = "myadminsecretkey"
)
var client = &http.Client{}
func main() {
http.HandleFunc("/signup", signupHandler)
http.HandleFunc("/login", loginHandler)
http.ListenAndServe("0.0.0.0:8086", nil)
}
func signupHandler(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
password := r.FormValue("password")
encodePassword := hmacPassword(password)
log.Println("email:", email, "password:", password, "hmac: ", encodePassword)
query := `mutation CreateUser($email: String!, $password: String!) {\n insert_user_one(object: {password: $password, email: $email}) {\n id\n }\n}\n`
body := `{"operationName": "CreateUser", "query": "` + query + `", "variables":` + `{"email": "` + email + `", "password": "` + encodePassword + `"}}`
log.Println(" signupBody:", body)
resp, err := graphqlPost(body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read request.Body failed, err:%v", err)
w.WriteHeader(resp.StatusCode)
} else {
w.Write(b)
}
}
func graphqlPost(body string) (*http.Response, error) {
req, err := http.NewRequest("POST", hasuraURL, strings.NewReader(body))
if err != nil {
log.Println("create request err:", err)
return nil, fmt.Errorf("build req:", err)
}
req.Header.Set("content-type", "application/json")
req.Header.Set("x-hasura-admin-secret", hasuraAdminSecret)
return client.Do(req)
}
func hmacPassword(password string) string {
mac := hmac.New(md5.New, []byte(hmacKey))
mac.Write([]byte(password))
encodePassword := hex.EncodeToString(mac.Sum([]byte("")))
return encodePassword
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
password := r.FormValue("password")
encodePassword := hmacPassword(password)
log.Println("email:", email, "password:", password, "hmac: ", encodePassword)
query := `query FindByEmail($email: String!) {\n user(where: {email: {_eq: $email}}) {\n password\n role\n \n id\n }\n}\n`
body := `{"operationName": "FindByEmail", "query": "` + query + `", "variables":` + `{"email": "` + email + `"}}`
resp, err := graphqlPost(body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read request.Body failed, err:%v", err)
w.WriteHeader(resp.StatusCode)
} else {
var user UserResponse
json.Unmarshal(bytes, &user)
if len(user.Data.User) < 1 {
w.Write([]byte("user not exists"))
return
}
if user.Data.User[0].Password != encodePassword {
w.Write([]byte("wrong password"))
return
}
if token, err := genToken(user.Data.User[0]); err != nil {
w.Write([]byte("build token error: " + err.Error()))
return
} else {
w.Write([]byte(token))
}
}
}
type Claims struct {
Name string
UserId string `json:"x-hasura-user-id"`
Role string `json:"x-hasura-default-role"`
Roles []string `json:"x-hasura-allowed-roles"`
jwt.StandardClaims
}
func genToken(user User) (string, error) {
expireTime := time.Now().Add(7 * 24 * time.Hour)
claims := &Claims{
Name: user.Name,
UserId: strconv.Itoa(user.Id),
Role: user.Role,
Roles: []string{user.Role},
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(), //过期时间
IssuedAt: time.Now().Unix(),
Issuer: "127.0.0.1", // 签名颁发者
Subject: "user token", //签名主题
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(jwtKey))
}
type UserResponse struct {
Data struct {
User []User `json:"user"`
} `json:"data"`
}
type User struct {
Password string `json:"password"`
Role string `json:"role"`
Name string `json:""`
Id int `json:"id"`
}