Golang - GraphQL - Authentication

Introduction

上一篇已经知道如何透过 GraphQL对 Database 进行操作
接下来要在 Application 中加入身分认证的功能,使用的是 JWT(Json Web Token)
如果没有接触过 JWT,更多的细节请见关于 JWT 的参考资料

个人觉得这个教学其实蛮好的,虽然我 JWT 跟 Database 的部分都实作过了
但是很推荐给想做 backend 的新手参考
这个教学基本上跟实际运作的方式相差无几,可以拿去应用在实战上

Sample Code

开始实作前先执行 $ go get github.com/dgrijalva/jwt-go 取得 JWT package
完成后的资料夹如下
http://img2.58codes.com/2024/20118878zcKaCzj3W0.png

Generating and Parsing JWT Tokens

pkg/jwt/jwt.go 实作能够产生与解析 JWT Token 的程式

package jwtimport ("github.com/dgrijalva/jwt-go""log""time")// secret key being used to sign tokensvar (SecretKey = []byte("secret"))// GenerateToken generates a jwt token and assign a username to it's claims and return itfunc GenerateToken(username string) (string, error) {token := jwt.New(jwt.SigningMethodHS256)/* Create a map to store our claims */claims := token.Claims.(jwt.MapClaims)/* Set token claims */claims["username"] = usernameclaims["exp"] = time.Now().Add(time.Hour * 24).Unix()tokenString, err := token.SignedString(SecretKey)if err != nil {log.Fatal("Error in Generating key")return "", err}return tokenString, nil}// ParseToken parses a jwt token and returns the username in it's claimsfunc ParseToken(tokenStr string) (string, error) {token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {return SecretKey, nil})if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {username := claims["username"].(string)return username, nil} else {return "", err}}

User SignUp and Login Functionality

internal/users/users.go 实作注册功能

package usersimport ("database/sql""github.com/glyphack/go-graphql-hackernews/internal/pkg/db/mysql""golang.org/x/crypto/bcrypt""log")type User struct {ID       string `json:"id"`Username     string `json:"name"`Password string `json:"password"`}func (user *User) Create() {statement, err := database.Db.Prepare("INSERT INTO Users(Username,Password) VALUES(?,?)")print(statement)if err != nil {log.Fatal(err)}hashedPassword, err := HashPassword(user.Password)_, err = statement.Exec(user.Username, hashedPassword)if err != nil {log.Fatal(err)}}//HashPassword hashes given passwordfunc HashPassword(password string) (string, error) {bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)return string(bytes), err}//CheckPassword hash compares raw password with it's hashed valuesfunc CheckPasswordHash(password, hash string) bool {err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))return err == nil}

Authentication Middleware

internal/users/users.go 加入 Query 程式码

//GetUserIdByUsername check if a user exists in database by given usernamefunc GetUserIdByUsername(username string) (int, error) {statement, err := database.Db.Prepare("select ID from Users WHERE Username = ?")if err != nil {log.Fatal(err)}row := statement.QueryRow(username)var Id interr = row.Scan(&Id)if err != nil {if err != sql.ErrNoRows {log.Print(err)}return 0, err}return Id, nil}

internal/auth/middleware.go 实作 HTTP server router middleware
只要使用到 API 都需要经过 middleware 认证

package authimport ("Go-GraphQL/internal/pkg/jwt""Go-GraphQL/internal/users""context""net/http""strconv")var userCtxKey = &contextKey{"user"}type contextKey struct {name string}func Middleware() func(http.Handler) http.Handler {return func(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {header := r.Header.Get("Authorization")// Allow unauthenticated users inif header == "" {next.ServeHTTP(w, r)return}//validate jwt tokentokenStr := headerusername, err := jwt.ParseToken(tokenStr)if err != nil {http.Error(w, "Invalid token", http.StatusForbidden)return}// create user and check if user exists in dbuser := users.User{Username: username}id, err := users.GetUserIdByUsername(username)if err != nil {next.ServeHTTP(w, r)return}user.ID = strconv.Itoa(id)// put it in contextctx := context.WithValue(r.Context(), userCtxKey, &user)// and call the next with our new contextr = r.WithContext(ctx)next.ServeHTTP(w, r)})}}// ForContext finds the user from the context. REQUIRES Middleware to have run.func ForContext(ctx context.Context) *users.User {raw, _ := ctx.Value(userCtxKey).(*users.User)return raw}

最后改写 main.go

package mainimport ("Go-GraphQL/graph""Go-GraphQL/internal/auth"mig "Go-GraphQL/internal/pkg/db/migrations/mysql""log""net/http""os""github.com/99designs/gqlgen/graphql/handler""github.com/99designs/gqlgen/graphql/playground""github.com/go-chi/chi")const defaultPort = "8080"func main() {port := os.Getenv("PORT")if port == "" {port = defaultPort}router := chi.NewRouter()router.Use(auth.Middleware())mig.InitDB()mig.Migrate()server := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))router.Handle("/", playground.Handler("GraphQL playground", "/query"))router.Handle("/query", server)log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)log.Fatal(http.ListenAndServe(":"+port, router))}

Reference

https://www.howtographql.com/graphql-go/6-authentication/https://jwt.io/

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章