概述

最近要在http接口上加一个token认证,但是接口很多,有没有一个省时省力的办法来解决。token的使用流程是:

  1. 用户使用帐号密码登陆到服务器1. 服务器验证登陆成功,根据帐号密码生成token。把token返回给客户端
  2. 客户端请求接口时需要带上token。服务器需要验证token。 接口是用golang的net/http库写的。为了实现目的,还需要添加的功能是:
  3. 生成和解析token的方法;
  4. 一个中间件,拦截请求,进行token认证,根据认证通是否过来决定是否继续执行请求; JWT身份认证可以解决第一个问题,mux.RouterUse方法函数可以作为一个中间件, 拦截请求。
func (r *Router) Use(mwf ...MiddlewareFunc) {
	for _, fn := range mwf {
		r.middlewares = append(r.middlewares, fn)
	}
}

JWT 身份认证

下载地址:

github.com/dgrijalva/jwt-go"

实现的功能如下:

  1. token生成
  2. 中间件拦截请求进行token认证 结合代码:
// jwt_svr.go
package main

import (
	"context"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/dgrijalva/jwt-go/request"
	"github.com/gorilla/mux"
	"net/http"
	"strings"
	"time"
)

//自定义Claims结构体
type LoginClaims struct {
	Uid  string
	Name string
	Pwd  string
	LoginTime time.Duration
	token string
	jwt.StandardClaims
}

//var nowDate = time.Now().Format("2020-02-22 15")
var nowDate = "123123123"
var secretKey = fmt.Sprintf("%v%v",nowDate,"dingding")

//生成token
func Sign(uid, name, pwd string) (string, error) {
	//开始生成token
	now := time.Now()
	exp := now.Add(time.Duration(2) * time.Minute)  //2分钟后过期
	claims := LoginClaims{
		Uid: 	uid,
		Name: 	name,
		Pwd : 	pwd,
		LoginTime : time.Duration(now.Unix()),
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: exp.Unix(),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secretKey))
}

//解析token
func ParseToken(token string, secret string) (*LoginClaims, error) {
	loginclaims := new(LoginClaims)
	claim, err := jwt.ParseWithClaims(token, loginclaims, func(token *jwt.Token) (interface{}, error) {
		return []byte(secret), nil
	})
	if err != nil {
		return loginclaims, err
	}
	if !claim.Valid {
		return loginclaims, fmt.Errorf("claim.Valid error")
	}
	return loginclaims, nil
}

func stripBearerPrefixFromTokenString(tok string ) (string, error)  {
	if len(tok) > 4 && strings.ToUpper(tok[0:4]) == "JWT " {
		return tok[4:], nil
	}
	return tok, nil
}

func main () {
	r := mux.NewRouter()
	//生成token
	t, err := Sign("1", "zjjj", "123")
	if nil != err {
		fmt.Errorf(err.Error())
		return
	}
	//解析token
	l, err := ParseToken(t, secretKey)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}

	fmt.Printf("generate token:  %s\n", t)
	fmt.Printf("uid:  %s\n", l.Uid)

	//中间件  拦截请求,用来token认证
	r.Use(func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			mapClaims := new(LoginClaims) //自定义类型
			var myToken string
			// 如果token存在于Authorization中
			token, err := request.ParseFromRequest(r, &request.PostExtractionFilter{
				Extractor: request.HeaderExtractor{"Authorization"},
				Filter:    stripBearerPrefixFromTokenString,
			}, func(token *jwt.Token) (interface{}, error) {
				return []byte(secretKey), nil
			}, request.WithClaims(mapClaims))

			if !token.Valid {
				// 如果token存在于header中
				for k, v := range r.Header {
					if strings.ToLower(k) == "token" {
						myToken = v[0]
						break
					}
				}
				mapClaims, err = ParseToken(myToken, secretKey)
				if err != nil {
					w.Write([]byte("token invalid\n"))
					return
				}
			}else {
				//myToken = strings.Split(r.Header["Authorization"][0], " ")[1] //第二种获取Authorization的方式
				fmt.Printf("id: %s\n", mapClaims.Uid)
			}
			//把解析好的token数据放到context中
			longtext := fmt.Sprintf("解析好的tokne数据 id: %s, name: %s, pwd: %s\n", mapClaims.Uid, mapClaims.Name, mapClaims.Pwd)
			w.Write([]byte(longtext))
			ctx := context.WithValue(r.Context(), "id", mapClaims.Uid)
			ctx = context.WithValue(ctx, "name", mapClaims.Name)
			r = r.WithContext(ctx)
			next.ServeHTTP(w, r)
		})
	})

	r.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		fmt.Printf("handleFunc test\n")
		w.Write([]byte("test is success"))
	})
    r.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		r.ParseForm()
		//fmt.Fprintln(w,r.Form)
		name := r.FormValue("name")
		pwd := r.FormValue("pwd")
		fmt.Printf("login: name: %s, pwd: %s\n", name, pwd)
		login, err := Sign("1", name, pwd)
		if nil != err {
			fmt.Errorf(err.Error())
			w.Write([]byte(err.Error()))
			return
		}
		w.Write([]byte(login))
	})
	fmt.Printf("Server listen on 8080...\n")
	http.ListenAndServe(":8080", r)
}

验证一下, 启动程序:

$ go run jwt_svr.go
generate token:  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVaWQiOiIxIiwiTmFtZSI6InpqamoiLCJQd2QiOiIxMjMiLCJMb2dpblRpbWUiOjE2MTQxMzg1MzIsImV4cCI6MTYxNDEzODY1Mn0.kHZKlpZXY_qLFaOfr8g1i9uPssjZB3vZFrMqx8S0Ef4
uid:  1
Server listen on 8080...

已经监听8080端口,使用idea的rest client来模拟请求,在Headers中加入

Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVaWQiOiIxIiwiTmFtZSI6InpqamoiLCJQd2QiOiIxMjMiLCJMb2dpblRpbWUiOjE2MTQxMzg1MzIsImV4cCI6MTYxNDEzODY1Mn0.kHZKlpZXY_qLFaOfr8g1i9uPssjZB3vZFrMqx8S0Ef4

点击 rest client的运行按钮,得到数据:

解析好的tokne数据 id: 1, name: zjjj, pwd: 123
test is success

第二种方法是请求login接口生成token,参数 name='', pwd='', 生成token后再按照上述方法,请求test接口

--完--