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" }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2023-11-22 OpenSSL - Generate random string