• 30.Jwt集成(4):请求tokenAPI、中间件的方式集成token认证、用户信息传递


    还是三步骤创建EndPoint,创建Transport,调用请求

    第一步创建transport

    package Services
    
    import (
        "context"
        "encoding/json"
        "errors"
        "github.com/tidwall/gjson"
        "io/ioutil"
        "net/http"
    )
    
    
    
    func DecodeAccessRequest(c context.Context, r *http.Request) (interface{}, error){
        body,_:=ioutil.ReadAll(r.Body)
        result:=gjson.Parse(string(body)) //第三方库解析json
        if result.IsObject() { //如果是json就返回true
            username:=result.Get("username")
            userpass:=result.Get("userpass")
            return AccessRequest{Username:username.String(),Userpass:userpass.String(),Method:r.Method},nil
        }
        return nil,errors.New("参数错误")
    
    }
    func EncodeAccessResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
        w.Header().Set("Content-type","application/json")
        return json.NewEncoder(w).Encode(response) //返回一个bool值判断response是否可以正确的转化为json,不能则抛出异常,返回给调用方
    }

    在UserTransport中修改一下代码

    package Services
    
    import (
        "context"
        "encoding/json"
        "errors"
        mymux "github.com/gorilla/mux"
        "gomicro/utils"
        "net/http"
        "strconv"
    )
    
    func DecodeUserRequest(c context.Context, r *http.Request) (interface{}, error) { //这个函数决定了使用哪个request来请求
        vars := mymux.Vars(r)
        if uid, ok := vars["uid"]; ok {
            uid, _ := strconv.Atoi(uid)
            return UserRequest{Uid: uid, Method: r.Method, Token: r.URL.Query().Get("token")}, nil //请求必须携带token过来,如果找不到这里返回空字符串,因为request访问的先后顺序是先DecodeUserRequest,再EncodeUserResponse再到我们的EndPoint,所以这里就已经给我们的request结构体存入了Token,那么我们EndPoint里面的request类型断言成UserRequest结构体实例后里面就有Token了
        }
        return nil, errors.New("参数错误")
    }
    
    func EncodeUserResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
        w.Header().Set("Content-type", "application/json")
        return json.NewEncoder(w).Encode(response)
    }
    
    func MyErrorEncoder(ctx context.Context, err error, w http.ResponseWriter) {
        contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
        w.Header().Set("Content-type", contentType) //设置请求头
        if myerr, ok := err.(*utils.MyError); ok {
            w.WriteHeader(myerr.Code)
            w.Write(body)
        } else {
            w.WriteHeader(500)
            w.Write(body)
        }
    
    }
    

    创建Endpoint,这段是生成Token的代码,我这里先调用一下这段代码,拿到token后使用localhost:8080/user?token=token来访问我们的接口

    package Services
    
    import (
        "context"
        "fmt"
        "github.com/dgrijalva/jwt-go"
        "github.com/go-kit/kit/endpoint"
        "time"
    )
    const secKey="123abc"//秘钥
    type UserClaim struct {
        Uname string `json:"username"`
        jwt.StandardClaims
    }
    type IAccessService interface {
        GetToken(uname string,upass string) (string,error)
    }
    type AccessService struct {}
    
    func(this * AccessService) GetToken (uname string,upass string ) (string,error)  {
        if uname=="jerry" && upass=="123"{
            userinfo:=&UserClaim{Uname:uname}
            userinfo.ExpiresAt=time.Now().Add(time.Second*60).Unix() //设置60秒的过期时间
            token_obj:=jwt.NewWithClaims(jwt.SigningMethodHS256,userinfo)
            token,err:=token_obj.SignedString([]byte(secKey))
            return token,err
        }
        return "",fmt.Errorf("error uname and password")
    }
    
    type AccessRequest struct {
        Username string
        Userpass string
        Method string
    }
    type AccessResponse struct {
        Status string
        Token string
    }
    func  AccessEndpoint(accessService IAccessService) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (response interface{}, err error) {
            r:=request.(AccessRequest)
            result:=AccessResponse{Status:"OK"}
            if r.Method=="POST"{
                token,err:=accessService.GetToken(r.Username,r.Userpass)
                if err!=nil{
                    result.Status="error:"+err.Error()
                }else{
                    result.Token=token
                }
            }
            return result,nil
        }
    }

    在UserEndPoint中增加CheckToken的中间件

    package Services
    
    import (
        "context"
        "fmt"
        "github.com/dgrijalva/jwt-go"
        "github.com/go-kit/kit/endpoint"
        "github.com/go-kit/kit/log"
        "golang.org/x/time/rate"
        "gomicro/utils"
        "strconv"
    )
    
    type UserRequest struct { //封装User请求结构体
        Uid    int `json:"uid"`
        Method string
        Token  string
    }
    
    type UserResponse struct {
        Result string `json:"result"`
    }
    
    //token验证中间件
    func CheckTokenMiddleware() endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                r := request.(UserRequest) //通过类型断言获取请求结构体
                uc := UserClaim{}
                //下面的r.Token是在代码DecodeUserRequest那里封装进去的
                getToken, err := jwt.ParseWithClaims(r.Token, &uc, func(token *jwt.Token) (i interface{}, e error) {
                    return []byte(secKey), err
                })
                fmt.Println(err, 123)
                if getToken != nil && getToken.Valid { //验证通过
                    newCtx := context.WithValue(ctx, "LoginUser", getToken.Claims.(*UserClaim).Uname)
                    return next(newCtx, request)
                } else {
                    return nil, utils.NewMyError(403, "error token")
                }
    
                //logger.Log("method", r.Method, "event", "get user", "userid", r.Uid)
    
            }
        }
    }
    
    //日志中间件,每一个service都应该有自己的日志中间件
    func UserServiceLogMiddleware(logger log.Logger) endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                r := request.(UserRequest) //通过类型断言获取请求结构体
                logger.Log("method", r.Method, "event", "get user", "userid", r.Uid)
                return next(ctx, request)
            }
        }
    }
    
    //加入限流功能中间件
    func RateLimit(limit *rate.Limiter) endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                if !limit.Allow() {
                    return nil, utils.NewMyError(429, "toot many request")
                }
                return next(ctx, request) //执行endpoint
            }
        }
    }
    
    func GenUserEnPoint(userService IUserService) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (response interface{}, err error) {
            r := request.(UserRequest) //通过类型断言获取请求结构体
            fmt.Println("当前登录用户为", ctx.Value("LoginUser"))
            result := "nothings"
            if r.Method == "GET" {
                result = userService.GetName(r.Uid) + strconv.Itoa(utils.ServicePort)
    
            } else if r.Method == "DELETE" {
                err := userService.DelUser(r.Uid)
                if err != nil {
                    result = err.Error()
                } else {
                    result = fmt.Sprintf("userid为%d的用户已删除", r.Uid)
                }
            }
            return UserResponse{Result: result}, nil
        }
    }
    

    调用checkToken中间件的代码

    package main
    
    import (
        "flag"
        "fmt"
        kitlog "github.com/go-kit/kit/log"
        httptransport "github.com/go-kit/kit/transport/http"
        mymux "github.com/gorilla/mux"
        "golang.org/x/time/rate"
        "gomicro/Services"
        "gomicro/utils"
        "log"
        "net/http"
        "os"
        "os/signal"
        "strconv"
        "syscall"
    )
    
    func main() {
        name := flag.String("name", "", "服务名称")
        port := flag.Int("port", 0, "服务端口")
        flag.Parse()
        if *name == "" {
            log.Fatal("请指定服务名")
        }
        if *port == 0 {
            log.Fatal("请指定端口")
        }
        var logger kitlog.Logger
        {
            logger = kitlog.NewLogfmtLogger(os.Stdout)
            logger = kitlog.WithPrefix(logger, "mykit", "1.0")
            logger = kitlog.WithPrefix(logger, "time", kitlog.DefaultTimestampUTC) //加上前缀时间
            logger = kitlog.WithPrefix(logger, "caller", kitlog.DefaultCaller)     //加上前缀,日志输出时的文件和第几行代码
    
        }
        utils.SetServiceNameAndPort(*name, *port) //设置服务名和端口
    
        //用户服务
        user := Services.UserService{}
        limit := rate.NewLimiter(1, 5)
        endp := Services.RateLimit(limit)(Services.UserServiceLogMiddleware(logger)(Services.CheckTokenMiddleware()(Services.GenUserEnPoint(user))))
    
        //增加handler用于获取token
        accessService := &Services.AccessService{}
        accessServiceEndpoint := Services.AccessEndpoint(accessService)
        accessHandler := httptransport.NewServer(accessServiceEndpoint, Services.DecodeAccessRequest, Services.EncodeAccessResponse)
    
        options := []httptransport.ServerOption{
            httptransport.ServerErrorEncoder(Services.MyErrorEncoder), //使用我们的自定义错误
        }
    
        serverHandler := httptransport.NewServer(endp, Services.DecodeUserRequest, Services.EncodeUserResponse, options...) //使用go kit创建server传入我们之前定义的两个解析函数
    
        r := mymux.NewRouter()
        //r.Handle(`/user/{uid:d+}`, serverHandler) //这种写法支持多种请求方式
        r.Methods("POST").Path("/access-token").Handler(accessHandler)            //注册token获取的handler
        r.Methods("GET", "DELETE").Path(`/user/{uid:d+}`).Handler(serverHandler) //这种写法仅支持Get,限定只能Get请求
        r.Methods("GET").Path("/health").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
            writer.Header().Set("Content-type", "application/json")
            writer.Write([]byte(`{"status":"ok"}`))
        })
        errChan := make(chan error)
        go func() {
            utils.RegService()                                                 //调用注册服务程序
            err := http.ListenAndServe(":"+strconv.Itoa(utils.ServicePort), r) //启动http服务
            if err != nil {
                log.Println(err)
                errChan <- err
            }
        }()
        go func() {
            sigChan := make(chan os.Signal)
            signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
            errChan <- fmt.Errorf("%s", <-sigChan)
        }()
        getErr := <-errChan
        utils.UnRegService()
        log.Println(getErr)
    }
    

    使用postman测试接口

    gjson适用于解析json的三方库,速度很快,可以取Github了解下

    现在我们拿到了token,那么现在我们在访问的时候需要带上token去访问接口在UserRequest中新加一个Token字段用于请求认证

    package Services
    
    import (
        "context"
        "fmt"
        "github.com/dgrijalva/jwt-go"
        "github.com/go-kit/kit/endpoint"
        "github.com/go-kit/kit/log"
        "golang.org/x/time/rate"
        "gomicro/utils"
        "strconv"
    )
    
    type UserRequest struct { //封装User请求结构体
        Uid    int `json:"uid"`
        Method string
        Token  string //新加的token字段,用于读取url中的token封装进来再传递给下一层的请求处理
    }
    
    type UserResponse struct {
        Result string `json:"result"`
    }
    
    //token验证中间件
    func CheckTokenMiddleware() endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                r := request.(UserRequest) //通过类型断言获取请求结构体
                uc := UserClaim{}
                getToken, err := jwt.ParseWithClaims(r.Token, &uc, func(token *jwt.Token) (i interface{}, e error) { //验证token是否正确
                    return []byte(secKey), err
                })
                if getToken != nil && getToken.Valid { //验证通过
                    //这里很关键,验证通过后我们把用户名通过ctx传入到下一层的请求,标识当前用户已经通过验证,即GenUserEndPoint返回的endpoint方法
                    newCtx := context.WithValue(ctx, "LoginUser", getToken.Claims.(*UserClaim).Uname)
                    return next(newCtx, request)
                } else {
                    return nil, utils.NewMyError(403, "error token")
                }
    
                //logger.Log("method", r.Method, "event", "get user", "userid", r.Uid)
    
            }
        }
    }
    
    //日志中间件,每一个service都应该有自己的日志中间件
    func UserServiceLogMiddleware(logger log.Logger) endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                r := request.(UserRequest) //通过类型断言获取请求结构体
                logger.Log("method", r.Method, "event", "get user", "userid", r.Uid)
                return next(ctx, request)
            }
        }
    }
    
    //加入限流功能中间件
    func RateLimit(limit *rate.Limiter) endpoint.Middleware { //Middleware type Middleware func(Endpoint) Endpoint
        return func(next endpoint.Endpoint) endpoint.Endpoint { //Endpoint type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
            return func(ctx context.Context, request interface{}) (response interface{}, err error) {
                if !limit.Allow() {
                    return nil, utils.NewMyError(429, "toot many request")
                }
                return next(ctx, request) //执行endpoint
            }
        }
    }
    
    func GenUserEnPoint(userService IUserService) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (response interface{}, err error) {
            r := request.(UserRequest) //通过类型断言获取请求结构体
            fmt.Println("当前登录用户为", ctx.Value("LoginUser")) //读取上面newCtx设置的用户name,判断能否处理请求,我这里是简写了,如果读不到应该拒绝处理
            result := "nothings"
            if r.Method == "GET" {
                result = userService.GetName(r.Uid) + strconv.Itoa(utils.ServicePort)
    
            } else if r.Method == "DELETE" {
                err := userService.DelUser(r.Uid)
                if err != nil {
                    result = err.Error()
                } else {
                    result = fmt.Sprintf("userid为%d的用户已删除", r.Uid)
                }
            }
            return UserResponse{Result: result}, nil
        }
    }
    

    启动server后使用postman使用localhost:8080/user/101访问结果如下,因为url没有携带token所以报错了

    接下来我们使用正确的方式再次访问

    一般我們把token存入到Redis中防止用戶一直請求,也起到了缓存的作用





  • 相关阅读:
    HTML基础知识笔记摘要
    Shiro安全框架的说明及配置入门
    空指针问题的解决
    Log4j的配置及使用
    有关于注解的说明
    SpringMVC整合mybatis基于纯注解配置
    使用springmvc进行文件的上传和下载
    数据库设计入门及ERMaster的安装和使用
    spring mvc 接收ajax 复杂结构数据
    idea git ignore 插件
  • 原文地址:https://www.cnblogs.com/hualou/p/12091739.html
Copyright © 2020-2023  润新知