auth0与go -gin后端与JWT中间件
#安全 #go #jwt #auth0

大家好,希望您过得愉快!
我们将使用Auth0 Golang JWT中间件使用Gin Web框架来查看如何使用Auth0 Golang JWT中间件来验证JWT。杜松子酒是用GO(Golang)编写的网络框架。在深入研究中,我们将看到如何集成Auth0 Golang JWT中间件以验证JWTS

此博客是@ksivamuthu blog
的Alter版本 多亏了@ksivamuthu,我写了一个了不起的博客,我正在写这个博客,因为我无法在互联网中找到我的问题的精确解决方案,希望今天对您有所帮助

如果您遵循上述博客,您将了解有关auth0的基础知识,杜松子酒框架

基本上
用户流就像这样
(将CLI作为客户端)和黑匣子为服务器
我正在使用(可选)

之间的代理

Image description

首先遵循此blog

您必须先创建一个应用程序,并且我正在使用命令行应用程序,因此我创建了一个本机应用程序,我们必须创建用户和用户角色并分配给用户,为此,我创建了一个用户和角色在Auth0应用中具有权限“ CRUD:ADVINDSTINGS”并在应用程序中更新回调URL,我正在使用“ http://localhost:4242/auth”作为回电,请确保在使用auth0

之前设置所有内容

您将auth0 api致电登录,如果成功登录,则给出一个令牌作为响应

基本上,我们必须将此JWT令牌用于我们的终点,但是在实际调用管理者之前,我们需要验证令牌和权限

示例备份的路由器GO代码

package api

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "strings"

    "github.com/gin-gonic/gin" 
    "github.com/lestrrat-go/jwx/jwk"
    "github.com/lestrrat-go/jwx/jwt"
)

func StartServer() {

    router := gin.Default() 


    ruoterGroup := router.Group("api/v1/product") //grouping the route end point for the handlers

    ruoterGroup.GET("", AuthorizeHandler(permission), GetHandler)
    ruoterGroup.POST("", AuthorizeHandler(permission), AddHandler)
    ruoterGroup.PUT("", AuthorizeHandler(permission), UpdateHandler)
    ruoterGroup.DELETE("", AuthorizeHandler(permission), DeleteHandler)

    baseURL := :"localhost:8080"
    router.Run(baseURL)
}

现在,您看到我们有一台在杜松子酒中创建的服务器和各自的处理程序,授权者应验证令牌并检查许可,在这里您可以拥有两个单独的功能来执行此操作,但我只创建了一个

现在在一个共同的地方我有这个函数定义

const (
    authHeader = "Authorization"
    permClaim  = "permissions"
)

var permission = "crud:admin-settings" //your permissions here
var auth0Domain = "dev-******.us.auth0.com" //your auth0 domain here
var audience = "http://auth0-local.com"

func AuthorizeHandler(permission string) gin.HandlerFunc {
    return func(c *gin.Context) {
        token, err := extractToken(c, auth0Domain)
        if err != nil {
            fmt.Println(err.Error())
        }
        if token == nil {
            fmt.Printf("failed to find token in context\n")
            c.JSON(http.StatusUnauthorized, "user hasn't logged in yet")
            c.Abort()
            return
        }
        if !tokenHasPermission(token, permission) {
            fmt.Printf("permission check failed\n")
            c.AbortWithStatusJSON(500, "error occurred when authorizing user")
            return
        }
        c.Next()
    }
}

func tokenHasPermission(token jwt.Token, permission string) bool {
    claims := token.PrivateClaims()
    tkPermissions, ok := claims[permClaim]
    if !ok {
        return false
    }
    tkPermList, ok := tkPermissions.([]interface{})
    if !ok {
        return false
    }
    for _, perm := range tkPermList {
        if perm == permission {
            return true
        }
    }
    return false
}

func extractToken(c *gin.Context, auth0Domain string) (jwt.Token, error) {
    // fetchTenantKeys fetch and parse the tenant JSON Web Keys (JWK). The keys
    // are used for JWT token validation during requests authorization.
    tenantKeys, err := jwk.Fetch(context.Background(),
        fmt.Sprintf("https://%s/.well-known/jwks.json", auth0Domain))
    if err != nil {
        log.Fatalf("failed to parse tenant json web keys: %s\n", err)
    }
    authorization := c.GetHeader(authHeader)
    if authorization == "" {
        return nil, errors.New("authorization header missing")
    }
    bearerAndToken := strings.Split(authorization, " ")
    if len(bearerAndToken) < 2 {
        return nil, errors.New("malformed authorization header: " + authorization)
    }
    token, err := jwt.Parse([]byte(bearerAndToken[1]), jwt.WithKeySet(tenantKeys),
        jwt.WithValidate(true), jwt.WithAudience(audience))
    if err != nil {
        return nil, err
    }
    return token, nil
}

最后,在我的客户端中,我有此代码来获取令牌,基本上它将打开浏览器并转到Auth0网站并进行身份验证并将其归还给您可以用于进一步的REST API调用的令牌

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "net"
    "net/http"
    "net/url"
    "os"
    "strings"

    cv "github.com/nirasan/go-oauth-pkce-code-verifier"
    "github.com/skratchdot/open-golang/open"
)

func main()  {
    //typically this values will come from config file
    AuthorizeUser("ghxc6OoNP**************HnLdqJN", "dev-*******.auth0.com","http://localhost:4242/auth")
}

// AuthorizeUser implements the PKCE OAuth2 flow.
func AuthorizeUser(clientID string, authDomain string, redirectURL string) {
    // initialize the code verifier
    var CodeVerifier, _ = cv.CreateCodeVerifier()

    // Create code_challenge with S256 method
    codeChallenge := CodeVerifier.CodeChallengeS256()

    // construct the authorization URL (with Auth0 as the authorization provider)
    authorizationURL := fmt.Sprintf(
        "https://%s/authorize?"+
            "&scope=openid"+
            "&response_type=code&client_id=%s"+
            "&code_challenge=%s"+
            "&code_challenge_method=S256&redirect_uri=%s",
        authDomain, clientID, codeChallenge, redirectURL)

    // start a web server to listen on a callback URL
    server := &http.Server{Addr: redirectURL}

    // define a handler that will get the authorization code, call the token endpoint, and close the HTTP server
    http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
        // get the authorization code
        code := r.URL.Query().Get("code")
        if code == "" {
            fmt.Println("snap: Url Param 'code' is missing")
            io.WriteString(w, "Error: could not find 'code' URL parameter\n")

            // close the HTTP server and return
            cleanup(server)
            return
        }

        // trade the authorization code and the code verifier for an access token
        codeVerifier := CodeVerifier.String()
        token, err := getAccessToken(clientID, codeVerifier, code, redirectURL)
        if err != nil {
            fmt.Println("snap: could not get access token")
            io.WriteString(w, "Error: could not retrieve access token\n")

            // close the HTTP server and return
            cleanup(server)
            return
        }

        //you can see the token
        fmt.Println(token) 

        io.WriteString(w, `
        <html>
            <body>
                <h1>Login successful!</h1>
                <h2>You can close this window</h2>
            </body>
        </html>`)

        fmt.Println("Successfully logged into snapmaster API.")

        // close the HTTP server
        cleanup(server)
    })

    // parse the redirect URL for the port number
    u, err := url.Parse(redirectURL)
    if err != nil {
        fmt.Printf("snap: bad redirect URL: %s\n", err)
        os.Exit(1)
    }

    // set up a listener on the redirect port
    port := fmt.Sprintf(":%s", u.Port())
    l, err := net.Listen("tcp", port)
    if err != nil {
        fmt.Printf("snap: can't listen to port %s: %s\n", port, err)
        os.Exit(1)
    }

    // open a browser window to the authorizationURL
    err = open.Start(authorizationURL)
    if err != nil {
        fmt.Printf("snap: can't open browser to URL %s: %s\n", authorizationURL, err)
        os.Exit(1)
    }

    // start the blocking web server loop
    // this will exit when the handler gets fired and calls server.Close()
    server.Serve(l)
}

// getAccessToken trades the authorization code retrieved from the first OAuth2 leg for an access token
func getAccessToken(clientID string, codeVerifier string, authorizationCode string, callbackURL string) (string, error) {
    // set the url and form-encoded data for the POST to the access token endpoint
    fmt.Println("authorizationCode:", authorizationCode)
    url := "https://dev-*******.us.auth0.com/oauth/token" //your auth0 domain
    data := fmt.Sprintf(
        "grant_type=authorization_code&client_id=%s"+
            "&code_verifier=%s"+
            "&code=%s"+
            "&redirect_uri=%s",
        clientID, codeVerifier, authorizationCode, callbackURL)
    payload := strings.NewReader(data)

    // create the request and execute it
    req, _ := http.NewRequest("POST", url, payload)
    req.Header.Add("content-type", "application/x-www-form-urlencoded")
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Printf("snap: HTTP error: %s", err)
        return "", err
    }

    // process the response
    defer res.Body.Close()
    var responseData map[string]interface{}
    body, _ := ioutil.ReadAll(res.Body)

    // unmarshal the json into a string map
    err = json.Unmarshal(body, &responseData)
    if err != nil {
        fmt.Printf("snap: JSON error: %s", err)
        return "", err
    } 
    // retrieve the access token out of the map, and return to caller
    //accessToken := responseData["access_token"].(string)
    return "", nil
}

// cleanup closes the HTTP server
func cleanup(server *http.Server) {
    // we run this as a goroutine so that this function falls through and
    // the socket to the browser gets flushed/closed before the server goes away
    go server.Close()
}

希望这将帮助一些人

感谢您的阅读,请评论如果您有任何疑问,
祝你有美好的一天