使用JWT保护Golang Rest API

JWT(JSON Web Token) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519)。JWT允许以无状态但安全的方式将信息从客户机传输到服务器,它特别适用于分布式站点的单点登录(SSO)场景。
前文介绍JWT实现机制,本文通过示例讲解JWT的一种应用场景。

JWT介绍

JWT标准使用加密算法要么使用使用HMAC算法,要么使用RSA或ECDSA的公钥/私钥对非对称加密算法。它在单页应用程序(SPA)中大量使用,作为安全通信的手段,因为它主要实现两个关键事项:

  • 身份认证

这是最常用的用法。一旦用户登录到应用程序、或者以某种方式进行了身份验证,那么代表用户从客户端发送的每个请求都将包含JWT。

  • 信息交换

JWT的第二个用途是在不同的系统之间安全地传输信息。这些JWT可以使用公钥/私钥对签名,能够以安全的方式验证该事务中的每个系统,并且JWT包含一个反篡改机制,因为它是基于头和负载签名的。

本文将详细介绍如何构建一个使用JSON Web token进行Golang的安全REST API通信!

JWT示例

我们打算使用Golang实现一个简单版本Rest方式的Hello World WEB应用。

代码如下:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Hello World")
    fmt.Println("Endpoint Hit: homePage")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":8081", nil))
}

func main() {
    handleRequests()
}

当我们访问 http://localhost:8081/ 时,可以在浏览器中看到 Hello World.

JWT认证

上面已经搭建了一个简单API接口,我们现在要使用JWT token进行保护。需要新建客户端API尝试请求Hello World
接口。我们对密钥进行签名生成JWT,使得服务端与客户端都不会看到:

  1. 客户端基于共享口令生成签名JWT
  2. 客户端再次访问服务端API,并把JWT作为请求的一部分
  3. 服务端读取请求JWT,并使用相同口令验证token
  4. 如果JWT有效,会返回受保护的Hello World消息,否则会返回未认证

服务端实现

下面实现服务端:


package main

import (
	"fmt"
	"log"
	"net/http"

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

var mySigningKey = []byte("captainjacksparrowsayshi")

func homePage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")
	fmt.Println("Endpoint Hit: homePage")

}

func isAuthorized(endpoint func(http.ResponseWriter, *http.Request)) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		if r.Header["Token"] != nil {

			token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("There was an error")
				}
				return mySigningKey, nil
			})

			if err != nil {
				fmt.Fprintf(w, err.Error())
			}

			if token.Valid {
				endpoint(w, r)
			}
		} else {
			fmt.Fprintf(w, "Not Authorized")
		}
	})
}

func handleRequests() {
	http.Handle("/", isAuthorized(homePage))
	log.Fatal(http.ListenAndServe(":9000", nil))
}

func main() {
	handleRequests()
}

现在我们解释上述代码。我们创建了非常简单的API,然后使用isAuthorized装饰中间件函数进行保护,在函数内检查请求头中是否有Token,如果有则进一步检查是否有效。如果有效则可以正常访问该地址,反之提示未授权。

客户端实现

完成了服务端API保护之后,下面编写客户端与之交互。下面代码定义一个简单客户端应用,尝试访问服务端/地址.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

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

var mySigningKey = []byte("captainjacksparrowsayshi")

func homePage(w http.ResponseWriter, r *http.Request) {
	validToken, err := GenerateJWT()
	if err != nil {
		fmt.Println("Failed to generate token")
	}

	client := &http.Client{}
	req, _ := http.NewRequest("GET", "http://localhost:9000/", nil)
	req.Header.Set("Token", validToken)
	res, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(w, "Error: %s", err.Error())
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Fprintf(w, string(body))
}

func GenerateJWT() (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)

	claims["authorized"] = true
	claims["client"] = "Elliot Forbes"
	claims["exp"] = time.Now().Add(time.Minute * 30).Unix()

	tokenString, err := token.SignedString(mySigningKey)

	if err != nil {
		fmt.Errorf("Something Went Wrong: %s", err.Error())
		return "", err
	}

	return tokenString, nil
}

func handleRequests() {
	http.HandleFunc("/", homePage)

	log.Fatal(http.ListenAndServe(":8001", nil))
}

func main() {
	handleRequests()
}

我们简要解释下上述代码。首先定义简单API入口,访问会触使用mySigningKey生成新的JWT,然后创建http client,并设置Token头信息为生成的JWT字符串。

首先运行服务端程序,使用浏览器访问http://localhost:9000,会返回Not Authorized,因为我们没有在请求头中设置token。然后我们运行客户端程序,访问http://localhost:8001,因为我们在代码中设置了token,因此可以正常访问并显示Hello World信息。

总结

本文介绍JWT的概念,并通过示例展示请求与响应过程中JWT的作用。参考内容:https://tutorialedge.net/golang/authenticating-golang-rest-api-with-jwts/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值