🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 概述 最近要在http接口上加一个token认证,但是接口很多,有没有一个省时省力的办法来解决。token的使用流程是: 1. 用户使用帐号密码登陆到服务器 2. 服务器验证登陆成功,根据帐号密码生成token。把token返回给客户端 3. 客户端请求接口时需要带上token。服务器需要验证token。 接口是用golang的`net/http`库写的。为了实现目的,还需要添加的功能是: 1. 生成和解析token的方法; 2. 一个中间件,拦截请求,进行token认证,根据认证通是否过来决定是否继续执行请求; `JWT`身份认证可以解决第一个问题,`mux.Router`的`Use`方法函数可以作为一个中间件, 拦截请求。 ```golang func (r *Router) Use(mwf ...MiddlewareFunc) { for _, fn := range mwf { r.middlewares = append(r.middlewares, fn) } } ``` ## `JWT`身份认证 下载地址: ```golang 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`来模拟请求`test`接口,在`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`接口