ZhangZhihui's Blog  

 

 

 

 

复制代码
const (
    ScopeActivation     = "activation"
    ScopeAuthentication = "authentication"
)

// Token holds the data for a token.
type Token struct {
    Plaintext string    `json:"token"`
    Hash      []byte    `json:"-"`
    UserID    int64     `json:"-"`
    Expiry    time.Time `json:"expiry"`
    Scope     string    `json:"-"`
}
复制代码

 

token.go:

复制代码
package main

import (
    "errors"
    "net/http"
    "time"

    "greenlight.zzh.net/internal/data"
    "greenlight.zzh.net/internal/validator"
)

func (app *application) createAuthenticationTokenHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Email    string `json:"email"`
        Password string `json:"password"`
    }

    err := app.readJSON(w, r, &input)
    if err != nil {
        app.badRequestResponse(w, r, err)
        return
    }

    v := validator.New()

    data.ValidateEmail(v, input.Email)
    data.ValidatePassword(v, input.Password)

    if !v.Valid() {
        app.failedValidationResponse(w, r, v.Errors)
        return
    }

    user, err := app.models.User.GetByEmail(input.Email)
    if err != nil {
        switch {
        case errors.Is(err, data.ErrRecordNotFound):
            app.invalidCredentialsResponse(w, r)
        default:
            app.serverErrorResponse(w, r, err)
        }
        return
    }

    match, err := user.Password.Matches(input.Password)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }
    if !match {
        app.invalidCredentialsResponse(w, r)
        return
    }

    token, err := app.models.Token.New(user.ID, 24*time.Hour, data.ScopeAuthentication)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }

    err = app.writeJSON(w, http.StatusCreated, envelope{"authentication_token": token}, nil)
    if err != nil {
        app.serverErrorResponse(w, r, err)
    }
}
复制代码

 

复制代码
zzh@ZZHPC:~$ BODY='{"email": "ZhangZhihuiAAA@126.com", "password": "pa55word"}'
zzh@ZZHPC:~$ curl -i -d "$BODY" localhost:4000/v1/tokens/authentication
HTTP/1.1 201 Created
Content-Type: application/json
Date: Fri, 22 Nov 2024 02:50:12 GMT
Content-Length: 143

{
    "authentication_token": {
        "token": "TXRO7FJ6L7XBXFK7O7SG4LWULM",
        "expiry": "2024-11-23T10:50:12.324997803+08:00"
    }
}
zzh@ZZHPC:~$ BODY='{"email": "alice@example.com", "password": "wrong pa55word"}'
zzh@ZZHPC:~$ curl -i -d "$BODY" localhost:4000/v1/tokens/authentication
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Date: Fri, 22 Nov 2024 02:56:01 GMT
Content-Length: 54

{
    "error": "invalid authentication credentials"
}
复制代码

 

复制代码
greenlight=> SELECT * FROM token WHERE scope = "authentication";
ERROR:  column "authentication" does not exist
LINE 1: SELECT * FROM token WHERE scope = "authentication";
                                          ^
greenlight=> SELECT * FROM token WHERE scope = 'authentication';
                                hash                                | user_id |         expiry         |     scope      
--------------------------------------------------------------------+---------+------------------------+----------------
 \xd8a13a29dc02a6b756887f61e21fecee3f83f66a936abd48a60e990d3c0c9343 |       9 | 2024-11-23 02:50:12+00 | authentication
(1 row)
复制代码

 

 

复制代码
var AnonymousUser = &User{}

// User represents an individual user.
type User struct {
    ID        int64     `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    Password  password  `json:"-"`
    Activated bool      `json:"activated"`
    Version   int       `json:"-"`
}

// IsAnonymous checks if a User instance is the AnonymousUser.
func (u *User) IsAnonymous() bool {
    return u == AnonymousUser
}
复制代码

 

复制代码
package main

import (
    "context"
    "net/http"

    "greenlight.zzh.net/internal/data"
)

type glContextKey string

// Convert the string "user" to a glContextKey type and assign it to the userContextKey constant.
// We'll use this constant as the key for getting and setting user information in the request 
// context.
const userContextKey = glContextKey("user")

// contextSetUser returns a new copy of the request with the provided User struct added to its 
// embedded context. 
func (app *application) contextSetUser(r *http.Request, user *data.User) *http.Request {
    ctx := context.WithValue(r.Context(), userContextKey, user)
    return r.WithContext(ctx)
}

// contextGetUser retrieves the User struct from the request context. The only time that we'll use 
// this helper is when we logically expect there to be User struct value in the context, and if it 
// doesn't exist it will firmly be an 'unexpected' error. It's OK to panic in those circumstances.
func (app *application) contextGetUser(r *http.Request) *data.User {
    user, ok := r.Context().Value(userContextKey).(*data.User)
    if !ok {
        panic("missing user value in request context")
    }

    return user
}
复制代码

 

In middleware.go:

复制代码
func (app *application) authenticate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Add the "Vary: Authorization" header to the response. This indicates to any caches that 
        // the response may vary based on the value of the Authorization header in the request.
        w.Header().Add("Vary", "Authorization")

        // Retrieve the value of the Authorization header from the request. 
        // This will return the empty string "" if there is no such header.
        authorizationHeader := r.Header.Get("Authorization")

        // If there is no Authorization header found, add the AnonymousUser to the request 
        // context. Then we call the next handler in the chain and return without executing 
        // any of the code below.
        if authorizationHeader == "" {
            r = app.contextSetUser(r, data.AnonymousUser)
            next.ServeHTTP(w, r)
            return
        }

        // Otherwise, try to split the Authorization header into its constituent parts. If the 
        // header isn't in the expected format, we return a 401 Unauthorized response.
        headerParts := strings.Split(authorizationHeader, " ")
        if len(headerParts) != 2 || headerParts[0] != "Bearer" {
            app.invalidAuthenticationTokenResponse(w, r)
            return
        }

        token := headerParts[1]

        v := validator.New()

        if data.ValidateTokenPlaintext(v, token); !v.Valid() {
            app.invalidAuthenticationTokenResponse(w, r)
            return
        }

        user, err := app.models.User.GetForToken(data.ScopeAuthentication, token)
        if err != nil {
            switch {
            case errors.Is(err, data.ErrRecordNotFound):
                app.invalidAuthenticationTokenResponse(w, r)
            default:
                app.serverErrorResponse(w, r, err)
            }
            return
        }

        r = app.contextSetUser(r, user)

        next.ServeHTTP(w, r)
    })
}
复制代码

 

In routes.go:

    // Wrap the router with middleware.
    return app.recoverPanic(app.rateLimit(app.authenticate(router)))

 

In errors.go:

func (app *application) invalidAuthenticationTokenResponse(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("WWW-Authenticate", "Bearer")

    message := "invalid or missing authentication token"
    app.errorResponse(w, r, http.StatusUnauthorized, message)
}

 

 

 

复制代码
zzh@ZZHPC:~$ curl -i localhost:4000/v1/healthcheck
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Authorization
Date: Fri, 22 Nov 2024 04:05:42 GMT
Content-Length: 123

{
    "status": "available",
    "system_info": {
        "environment": "development",
        "version": "1.0.0"
    }
}
复制代码

 

复制代码
zzh@ZZHPC:~$ curl -i -d '{"email": "ZhangZhihuiAAA@126.com", "password": "pa55word"}' localhost:4000/v1/tokens/authentication
HTTP/1.1 201 Created
Content-Type: application/json
Vary: Authorization
Date: Fri, 22 Nov 2024 04:08:02 GMT
Content-Length: 143

{
    "authentication_token": {
        "token": "RULKBNEGFJBHRE3ADD7HUGVHSQ",
        "expiry": "2024-11-23T12:08:02.227320413+08:00"
    }
}

zzh@ZZHPC:~$ curl -i -H "Authorization: Bearer RULKBNEGFJBHRE3ADD7HUGVHSQ" localhost:4000/v1/healthcheck
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Authorization
Date: Fri, 22 Nov 2024 04:09:29 GMT
Content-Length: 123

{
    "status": "available",
    "system_info": {
        "environment": "development",
        "version": "1.0.0"
    }
}
复制代码

 

复制代码
zzh@ZZHPC:~$ curl -i -H "Authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXX" localhost:4000/v1/healthcheck
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Vary: Authorization
Www-Authenticate: Bearer
Date: Fri, 22 Nov 2024 04:11:55 GMT
Content-Length: 59

{
    "error": "invalid or missing authentication token"
}

zzh@ZZHPC:~$ curl -i -H "Authorization: INVALID" localhost:4000/v1/healthcheck
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Vary: Authorization
Www-Authenticate: Bearer
Date: Fri, 22 Nov 2024 04:12:24 GMT
Content-Length: 59

{
    "error": "invalid or missing authentication token"
}
复制代码

 

posted on   ZhangZhihuiAAA  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-11-22 OpenSSL - Generate random string
 
点击右上角即可分享
微信分享提示