graphql学习(五)

简介: 今天给之前的demo增加登录验证. 现在验证流行使用JWT(JSON web tokens),我们也选择用github.com/dgrijalva/jwt-go. 还是从models开始,增加user.

今天给之前的demo增加登录验证.

现在验证流行使用JWT(JSON web tokens),我们也选择用github.com/dgrijalva/jwt-go.

还是从models开始,增加user.go,内容如下:

package models

import "errors"

type User struct {
    Id       int    `json:"id"`
    Username string `json:"username"`
    Password string `json:"password"`
}

var accountsMock = []User{
    User{
        Id:       1,
        Username: "admin",
        Password: "1234",
    },
    User{
        Id:       2,
        Username: "guest",
        Password: "5678",
    },
}

// Get User Info
func (u *User) GetUserByID(id int) (*User, error) {
    for _, user := range accountsMock {
        if user.Id == id {
            return &user, nil
        }
    }
    return nil, errors.New("User not found")
}

// 校验用户名与密码
func (u *User) Authenticate() (*User, error) {
    for _, user := range accountsMock {
        if user.Username == u.Username && user.Password == u.Password {
            return &user, nil
        }
    }

    return nil, errors.New("User not found or password is wrong")
}

新建utils目录,增加jwt.go文件,里面的三个函数一个是生成token,一个是解析token,最后一个是验证token的有效性:

package utils

import (
    "errors"
    "time"

    jwt "github.com/dgrijalva/jwt-go"
)

var jwtSecret = []byte("graphqldemo")

type Claims struct {
    Username string `json:"username"`
    Password string `json:"password"`
    jwt.StandardClaims
}

func GenerateToken(username, password string) (string, error) {
    nowTime := time.Now()
    expireTime := nowTime.Add(168 * time.Hour) // 有效期一周

    claims := Claims{
        username,
        password,
        jwt.StandardClaims{
            ExpiresAt: expireTime.Unix(),
            Issuer:    "demo",
        },
    }

    tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    token, err := tokenClaims.SignedString(jwtSecret)

    return token, err
}

func ParseToken(token string) (*Claims, error) {
    tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })

    if tokenClaims != nil {
        if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
            return claims, nil
        }
    }

    return nil, err
}

func ValidateJWT(token string) error {
    if token == "" {
        return errors.New("Authorization token must be present")
    }

    claims, err := ParseToken(token)
    if err != nil {
        return err
    } else if time.Now().Unix() > claims.ExpiresAt {
        return errors.New("Error. Token is expired")
    }

    return nil
}

在utils目录下,还要新建一个文件auth.go,主要给login的handler func:

package utils

import (
    "encoding/json"
    "graphqldemo/models"
    "log"
    "net/http"
)

// login handler
func CreateTokenEndpoint(response http.ResponseWriter, request *http.Request) {
    var user models.User
    _ = json.NewDecoder(request.Body).Decode(&user)

    if _, err := user.Authenticate(); err != nil {
        log.Println(err)
        response.WriteHeader(http.StatusUnauthorized)
        result := `{
    "code": 401,
    "msg": "未授权的访问",
}`
        response.Write([]byte(result))
        return
    }

    token, err := GenerateToken(user.Username, user.Password)
    if err != nil {
        log.Println(err)
    }

    response.Header().Set("content-type", "application/json")
    response.Header().Set("token", token)
    response.Write([]byte(`{ "token": "` + token + `" }`))
}

"_ = json.NewDecoder(request.Body).Decode(&user)" 表明我们传递用户名和密码到后端的时候,Content-Type:application/json, 如果是在postman里面,应该要按如下的格式:
image

    response.Header().Set("token", token)
    response.Write([]byte(`{ "token": "` + token + `" }`))

这两句表示通过验证后,可以在header和body里看到token的内容.如上图所示.

然后我们要对schema做一些修改,首先增加schema.go文件,把以下内容从article.go中转移到这里,这样当我们新建user.go后,修改或者增加schema.go里的query和mutation部分的"Fields: graphql.Fields{}"


package schema

import "github.com/graphql-go/graphql"

// query
// 定义根查询节点及各种查询
var rootQuery = graphql.NewObject(graphql.ObjectConfig{
    Name:        "RootQuery",
    Description: "Root Query",
    Fields: graphql.Fields{
        "articles": &queryArticles,
        "article":  &queryArticle,
    },
})

// mutation
// 定义增删改方法
var mutationType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "mutation",
    Description: "增删改",
    Fields: graphql.Fields{
        "add":    &addArticle,
        "update": &updateArticle,
        "delete": &deleteArticle,
    },
})

// 定义Schema用于http handler处理
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
    Query:    rootQuery,
    Mutation: mutationType,
})

我们修改schema/article.go文件,拿查看单篇文章详情来做测试:

// 查询单篇文章
var queryArticle = graphql.Field{
    Name:        "QueryArticle",
    Description: "Query Article",
    Type:        articleType,
    Args: graphql.FieldConfigArgument{
        "id": &graphql.ArgumentConfig{
            Type: graphql.Int,
        },
    },
    // Resolve是一个处理请求的函数,具体处理逻辑可在此进行
    Resolve: func(p graphql.ResolveParams) (result interface{}, err error) {
        // 在这里获取token并验证
        err = utils.ValidateJWT(p.Context.Value("token").(string))
        if err != nil {
            return nil, err
        }

        id, ok := p.Args["id"].(int)
        if !ok {
            return nil, errors.New("missing required arguments: id. ")
        }

        result, err = models.GetArticleByID(id)

        return result, err
    },
}

我们在Resolve里增加了这么一段:

err = utils.ValidateJWT(p.Context.Value("token").(string))
        if err != nil {
            return nil, err
        }

最最关键的是,p.Context.Value("token")的数据怎么传进来呢? 我在这折腾了好久.
最后修改main.go.

  1. 增加login的路由和handler
  2. 修改graphql的handler. 因为要传token,之前处理跨域的方式行不通了,所以改成如下的形式.
  3. 初始化handler部分,改成
GraphiQL:   false,
Playground: true,

主要是因为GraphiQL不知道怎么设置request header,所以改成用Playground. 必须要有Playground或者GraphiQL, 否则之前说的采用graphql的优势之一:接口文档就没有地方查看了. 平时测试接口,还是习惯使用postman.

package main

import (
    "context"
    "graphqldemo/schema"
    "graphqldemo/utils"
    "log"
    "net/http"

    "github.com/graphql-go/handler"
)

// main
func main() {
    h := Register()

    // 跨域
    c := cors.New(cors.Options{
        AllowedOrigins:   []string{"http://localhost:8080"},
        AllowedHeaders:   []string{"*"},
        AllowCredentials: true,
        // Debug:            true,
    })

    http.Handle("/graphql", c.Handler(h))
    http.HandleFunc("/login", utils.CreateTokenEndpoint)
    log.Println("Now server is running on port 9090")
    log.Fatal(http.ListenAndServe(":9090", nil))
}

// 初始化handler
func Register() http.Handler {
    h := handler.New(&handler.Config{
        Schema:     &schema.Schema,
        Pretty:     true,
        GraphiQL:   false,
        Playground: true,
    })

    // token传参
    hdl := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.Background()
        ctx = context.WithValue(ctx, "token", r.Header.Get("token"))
        h.ContextHandler(ctx, w, r)
    })

    return hdl
}

这样改造后,api应该可以用了.我们用postman来测试.

  1. 首先是login,如之前的图示,获取token.
  2. 请求如果不带上token
    image
  3. 请求带上错误的token
    image
  4. 请求带上正确的token
    image
目录
相关文章
|
29天前
|
数据采集 机器学习/深度学习 人工智能
关于数据集的采集、清理与数据,看这篇文章就够了
本文用通俗语言解析AI“隐形王者”——数据集,涵盖本质价值、三类数据形态、全生命周期七步法(需求定义→采集→清洗→标注→存储→划分→评估),并以垃圾评论拦截为例手把手实操。强调“数据即新石油”,质量决定模型上限。
145 16
|
存储 数据采集 数据管理
一体化元数据管理平台——OpenMetadata入门宝典
一体化元数据管理平台——OpenMetadata入门宝典
3767 0
|
10月前
|
Docker 容器 Perl
云效flow构建docker镜像更换apt源为阿里镜像源
在 Dockerfile 中添加命令以更换 Debian 源为阿里云镜像,加速容器内软件包下载。核心命令通过 `sed` 实现源地址替换,并更新 apt 软件源。其中 `cat` 命令用于验证替换是否成功,实际使用中可删除该行。
2015 32
|
10月前
|
机器学习/深度学习 存储 人工智能
三问一图万字拆解DeepSeek-R1:训练之道、实力之源与市场之变
本文是作者基于自己的学习经历重新组织的一篇更易于初心者理解的关于DeepSeek的文章,也可以说是作者阶段性的学习笔记。
559 43
三问一图万字拆解DeepSeek-R1:训练之道、实力之源与市场之变
|
10月前
|
人工智能 自然语言处理 Cloud Native
Bolt.diy 评测方案:从部署到创意实践的全方位探索
Bolt.diy 是阿里云推出的低代码开发平台,基于函数计算(FC)与百炼大模型服务构建。它通过自然语言交互、全栈开发支持及快速云端部署,让开发者和非技术人员能轻松实现创意落地。本文详细解析了 Bolt.diy 的部署流程、功能实践与应用场景,并结合测试案例探讨其价值与优化方向。无论是在教育、企业内部工具定制还是个人兴趣开发中,Bolt.diy 均展现出高效便捷的优势,但复杂业务需求仍需传统工具补充。未来,随着大模型能力升级,Bolt.diy 将进一步推动 AI 辅助开发的发展。
|
5月前
|
传感器 人工智能 安全
AR 巡检在工业的应用|阿法龙XR云平台
AR巡检技术广泛应用于电力、石化、制造、交通等行业,通过AR眼镜或平板实时叠加设备参数、历史数据及操作指引,提升巡检效率与准确性。支持远程协作、自动记录分析,并可在高危环境实现无人巡检,大幅降低安全风险,推动智能化运维升级。
|
数据采集 人工智能 数据管理
12款开源数据资产(元数据)管理平台选型分析(二)
12款开源数据资产(元数据)管理平台选型分析(二)
3655 0
|
9月前
|
API
鸿蒙开发:实现Popup气泡提示
原生的bindPopup属性,不仅仅支持单一的文字提示,也支持自定义组件的形式,已经可以满足正常的需求开发,能用原生的就用原生,之所以dialog库中增加了一个popup气泡弹窗,是因为当时封装的时候,原生还不支持自定义组件形式,如今已经支持了,大家可以放心的使用原生即可。
294 4
鸿蒙开发:实现Popup气泡提示
|
Linux Docker 容器
docker 国内镜像源
【8月更文挑战第26天】
5761 1
|
安全 Java Spring
springboot整合spring security 实现用户登录注册与鉴权全记录
【1月更文挑战第11天】springboot整合spring security 实现用户登录注册与鉴权全记录
2169 2