Web应用中的Go身份验证逻辑模式

I want to determine a simple and useful pattern for user authentication in a web app being written in golang.

I have come up with two patterns. First one is enabling the programmer to have his functions separate form the authentication logic, and has cleaner HandleFunc parts in main() that one can see only by loking main() to see what parts are under authentication control.

Second one is making programmer include a decision in every function deal with authentication required urls. An if statement checks by a authp() function defined else where.

Which one is better pattern for such necessity?

What are the better patterns for this job?

Is it even possible to pass a function to http.HandleFunc that has signature other than func urlFunc (ResponseWriter, *Request) bu like func urlFunc (successFunc, failFunc) or func urlFunc (ResponseWriter, *Request, successFunc, failFunc) as in authenticationGateKeeper function of First Way below, if not a suitable workaround for that?

//First Way
package main

func authGateKeeper(successFunc, failFunc) {
    if (authp()) {
        successFunc
    } else {
        failFunc
    }
}

func authp() boolean {
    //authentication logic, db query, or session check etc.
}

//usage in main
http.HandleFunc("/", authGateKeeper)



//Second Way; other alternative, in each function check pattern
func f(w, r) {
    if (authp()) {
    //function's processes
    } else {
    //the fail case function or processes
    }
}

func authp() boolean {
    //authentication logic, db query, or session check etc.
}

//usage in main
http.HandleFunc("/", f)

There are many ways to spin this, and it's arguable whether one is outright "better". I'd strongly suggest writing some middleware that wraps your routes and enforces the check, calling the wrapped handler only on success.

Note that I'm going to make a few assumptions here as you haven't told us how you're managing sessions (cookies? server-side?) and/or what kind of authorization you might need on top of authentication.

// Middleware - a function that sits in the 'middle' of your request processing.
func RequireAuth(h http.Handler) http.Handler) {
    fn := func(w http.ResponseWriter, r *http.Request) {
        // Assuming gorilla/sessions
        session, err := store.Get("name", r)
        if err != nil {
            // Raise HTTP 500
            return
        }

        // We'll assume you're storing the userID in the cookie|server session
        // upon login elsewhere.
        id := session.Values["userID"]
        // Probably returns a *yourapp.User
        user, err := db.GetUser(id)
        if err != nil {
            // Raise HTTP 500
            return
        }

        if user == nil {
            http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            // Don't forget these 'naked' returns - if you miss one, your 
            // handler will keep processing beyond the error and result in
            // unintended side effects
            return
        }

        // Further checks here - i.e. checking user.Active == true, etc.

        // The userID matches one in the DB, so let's proceed
        h.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

// And in your router - assuming just vanilla net/http
http.Handle("/", RequireAuth(yourHandlerFunc))
http.Handle("/", RequireAuth(someOtherHandler))
// Note that using gorilla/mux or goji can help give you "subrouters" so you
// don't have to wrap every single route with your middleware (messy, error prone)

I'd also suggest some reading on Go middleware1 composition2 which will help you in the future.

If you want to call a custom error page, just write a handler - e.g. UnauthorizedHandler that satisfies http.Handler and just call UnauthorizedHandler.ServeHTTP(w, r) instead of http.Error along the way.