introduce
Add Basic Auth middleware to gogf/gf microservice with a complete example .
What is HTTP Basic Auth Middleware?
The Basic Auth middleware will intercept each API request and verify the Basic Auth or X-API-Key authentication.
We will use rk-boot to start the gogf/gf microservice . rk-boot is a framework for launching various web services via YAML. Please refer to the last chapter of this article for details on rk-boot .
Please visit the following address for the complete tutorial: https://rkdocs.netlify.app/cn
Install
go get github.com/rookie-ninja/rk-boot/gf
quick start
1. Create boot.yaml
The boot.yaml file describes the meta information required by the gogf/gf framework to start.
To verify, we enabled the following options:
- commonService : commonService contains a series of common APIs. Details
- interceptors.auth : Basic Auth middleware, the default username and password are user:pass.
---
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional
interceptors:
auth:
enabled: true # Optional
basic: ["user:pass"] # Optional
2. Create main.go
Added /v1/greeter API.
If you want to add Swagger validation options to your API, please refer to swag security official website, we will introduce it in other examples.
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-boot/gf"
"net/http"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Bootstrap
boot.Bootstrap(context.Background())
// Register handler
gfEntry := rkbootgf.GetGfEntry("greeter")
gfEntry.Server.BindHandler("/v1/greeter", func(ctx *ghttp.Request) {
ctx.Response.WriteHeader(http.StatusOK)
ctx.Response.WriteJson(&GreeterResponse{
Message: fmt.Sprintf("Hello %s!", ctx.GetQuery("name")),
})
})
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
type GreeterResponse struct {
Message string
}
3. Folder structure
$ tree
.
├── boot.yaml
├── go.mod
├── go.sum
└── main.go
0 directories, 4 files
4. Start main.go
$ go run main.go
2022-01-04T17:45:56.925+0800 INFO boot/gf_entry.go:1050 Bootstrap gfEntry {"eventId": "984ea465-22cf-4cf3-a734-893fd3dc79e1", "entryName": "greeter"}
------------------------------------------------------------------------
endTime=2022-01-04T17:45:56.925701+08:00
startTime=2022-01-04T17:45:56.924974+08:00
elapsedNano=726802
timezone=CST
ids={"eventId":"984ea465-22cf-4cf3-a734-893fd3dc79e1"}
app={"appName":"rk","appVersion":"","entryName":"greeter","entryType":"GfEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"commonServiceEnabled":true,"commonServicePathPrefix":"/rk/v1/","gfPort":8080}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE
5. Verify
Without providing Basic Auth, we got a 401 error code.
401
$ curl -X GET localhost:8080/rk/v1/healthy
{
"error":{
"code":401,
"status":"Unauthorized",
"message":"Missing authorization, provide one of bellow auth header:[Basic Auth]",
"details":[]
}
}
$ curl -X GET "localhost:8080/v1/greeter?name=rk-dev
{
"error":{
"code":401,
"status":"Unauthorized",
"message":"Missing authorization, provide one of bellow auth header:[Basic Auth]",
"details":[]
}
}
200
Provides Basic Auth. For security reasons, the Auth in the Request Header needs to be encoded with Base64. We Base64 encode the user:pass string.
$ curl localhost:8080/rk/v1/healthy -H "Authorization: Basic dXNlcjpwYXNz"
{"healthy":true}
$ curl "localhost:8080/v1/greeter?name=rk-dev" -H "Authorization: Basic dXNlcjpwYXNz"
{"Message":"Hello rk-dev!"}
Authorize with X-API-Key
1. Modify boot.yaml
In this step, we start X-API-Key, and the value of key is token.
---
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional
interceptors:
auth:
enabled: true # Optional
apiKey: ["token"] # Optional
2. Start main.go
$ go run main.go
2022-01-04T17:53:44.120+0800 INFO boot/gf_entry.go:1050 Bootstrap gfEntry {"eventId": "161515d7-d2fb-4457-a24d-590edeb71bdf", "entryName": "greeter"}
------------------------------------------------------------------------
endTime=2022-01-04T17:53:44.12083+08:00
startTime=2022-01-04T17:53:44.120248+08:00
elapsedNano=582008
timezone=CST
ids={"eventId":"161515d7-d2fb-4457-a24d-590edeb71bdf"}
app={"appName":"rk","appVersion":"","entryName":"greeter","entryType":"GfEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"commonServiceEnabled":true,"commonServicePathPrefix":"/rk/v1/","gfPort":8080}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE
3. Verify
In the same situation, without providing the X-API-Key, we get a 401 error code.
401
$ curl localhost:8080/rk/v1/healthy
{
"error":{
"code":401,
"status":"Unauthorized",
"message":"Missing authorization, provide one of bellow auth header:[X-API-Key]",
"details":[]
}
}
$ curl "localhost:8080/v1/greeter?name=rk-dev"
{
"error":{
"code":401,
"status":"Unauthorized",
"message":"Missing authorization, provide one of bellow auth header:[X-API-Key]",
"details":[]
}
}
200
$ curl localhost:8080/rk/v1/healthy -H "X-API-Key: token"
{"healthy":true}
$ curl "localhost:8080/v1/greeter?name=rk-dev" -H "X-API-Key: token"
{"Message":"Hello rk-dev!"}
ignore request path
We can add a series of API request paths and let the middleware ignore validating these API requests.
---
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
commonService:
enabled: true # Optional
interceptors:
auth:
enabled: true # Optional
basic: ["user:pass"] # Optional
ignorePrefix: ["/rk/v1/healthy", "/v1/greeter"] # Optional
Swagger UI
How to add Basic Auth & X-API-Key input box in Swagger UI?
We use swag to generate the config files needed by Swagger UI, so add some comments to the code .
- Add the following annotation to the main() function to define security.
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
- Add the following annotation to the Handler function.
// @Security ApiKeyAuth
// @Security BasicAuth
complete example
- main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"github.com/labstack/echo/v4"
"github.com/rookie-ninja/rk-boot"
"net/http"
)
// @title RK Swagger for Echo
// @version 1.0
// @description This is a greeter service with rk-boot.
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Register handler
boot.GetEchoEntry("greeter").Echo.GET("/v1/greeter", Greeter)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
// @Summary Greeter service
// @Id 1
// @version 1.0
// @produce application/json
// @Param name query string true "Input name"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Success 200 {object} GreeterResponse
// @Router /v1/greeter [get]
func Greeter(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, &GreeterResponse{
Message: fmt.Sprintf("Hello %s!", ctx.QueryParam("name")),
})
}
type GreeterResponse struct {
Message string
}
- run the swag command
$ swag init
$ tree
.
├── boot.yaml
├── docs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
└── main.go
1 directory, 7 files
- boot.yaml add sw.enabled & sw.jsonPath (path to swagger.json file)
---
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
sw:
enabled: true # Optional
jsonPath: "docs" # Optional
interceptors:
auth:
enabled: true # Optional
basic: ["user:pass"] # Optional
Run main.go and visit http://localhost:8080/sw
Introduction to rk-boot
rk-boot is a framework for launching various web services via YAML. Somewhat similar to Spring boot. By integrating the rk-xxx series of libraries, various web frameworks can be started. Of course, users can also customize the rk-xxx library and integrate it into rk-boot.
rk-boot highlights
Start different web frameworks through YAML files in the same format.
For example, we can start gRPC, Gin, Echo, GoFrame frameworks simultaneously in one process through the following files. Unify the microservice layout within the team.
- Dependency installation
go get github.com/rookie-ninja/rk-boot/grpc
go get github.com/rookie-ninja/rk-boot/gin
go get github.com/rookie-ninja/rk-boot/echo
go get github.com/rookie-ninja/rk-boot/gf
- boot.yaml
---
grpc:
- name: grpc-server
port: 8080
enabled: true
commonService:
enabled: true
gin:
- name: gin-server
port: 8081
enabled: true
commonService:
enabled: true
echo:
- name: echo-server
port: 8082
enabled: true
commonService:
enabled: true
gf:
- name: gf-server
port: 8083
enabled: true
commonService:
enabled: true
- main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"github.com/rookie-ninja/rk-boot"
_ "github.com/rookie-ninja/rk-boot/echo"
_ "github.com/rookie-ninja/rk-boot/gf"
_ "github.com/rookie-ninja/rk-boot/gin"
_ "github.com/rookie-ninja/rk-boot/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
- verify
# gRPC throuth grpc-gateway
$ curl localhost:8080/rk/v1/healthy
{"healthy":true}
# Gin
$ curl localhost:8081/rk/v1/healthy
{"healthy":true}
# Echo
$ curl localhost:8082/rk/v1/healthy
{"healthy":true}
# GoFrame
$ curl localhost:8083/rk/v1/healthy
{"healthy":true}
Web frameworks supported by rk-boot
Contributions of new web frameworks to the rk-boot series are welcome.
Refer to docs & rk-gin for example.
frame | development status | Install | rely |
---|---|---|---|
Gin | Stable | go get github.com/rookie-ninja/rk-boot/gin | rk-gin |
gRPC | Stable | go get github.com/rookie-ninja/rk-boot/grpc | rk-grpc |
Echo | Stable | go get github.com/rookie-ninja/rk-boot/echo | rk-echo |
GoFrame | Stable | go get github.com/rookie-ninja/rk-boot/gf | rk-gf |
Fiber | Testing | go get github.com/rookie-ninja/rk-boot/fiber | rk-fiber |
go-zero | Testing | go get github.com/rookie-ninja/rk-boot/zero | rk-zero |
GorillaMux | Testing | go get github.com/rookie-ninja/rk-boot/mux | rk-mux |
Introduction to rk-gf
rk-gf is used to start the gogf/gf web service via YAML .
Supported Features
The following instances are initialized according to the YAML file. If it is external, the original usage is maintained.
example | introduce |
---|---|
ghttp.Server | Primitive gogf / gf |
Config | Native spf13/viper parameter example |
Logger | Native uber-go/zap log instance |
EventLogger | For logging RPC requests, use rk-query |
Credential | Used to pull Credential from remote services such as ETCD |
Cert | Get a TLS/SSL certificate from a remote service (ETCD, etc.) and start SSL/TLS |
Prometheus | Start the Prometheus client and push to the pushgateway as needed |
Swagger | Start Swagger UI locally |
CommonService | Expose common API |
TV | TV web page, showing basic information about microservices |
StaticFileHandler | Start the static file download service in the form of Web, the background storage supports the local file system and pkger. |
Supported middleware
rk-gf will initialize the middleware according to the YAML file.
Middleware | Description |
---|---|
Metrics | Collect RPC Metrics and start prometheus |
Log | Use rk-query to log each RPC log |
Trace | Collect RPC call chain and send data to stdout, local file or jaeger open-telemetry/opentelemetry-go . |
Panic | Recover from panic for RPC requests and log it. |
Meta | Collect service meta information and add it to the returned Header |
Auth | Support [Basic Auth] & [API Key] authentication middleware |
RateLimit | RPC rate limiting middleware |
Timeout | RPC timeout middleware |
CORS | CORS middleware |
JWT | JWT validation |
Secure | Server-side security middleware |
CSRF | CSRF middleware |
GoFrame full YAML configuration
---
#app:
# description: "this is description" # Optional, default: ""
# keywords: ["rk", "golang"] # Optional, default: []
# homeUrl: "http://example.com" # Optional, default: ""
# iconUrl: "http://example.com" # Optional, default: ""
# docsUrl: ["http://example.com"] # Optional, default: []
# maintainers: ["rk-dev"] # Optional, default: []
#zapLogger:
# - name: zap-logger # Required
# description: "Description of entry" # Optional
#eventLogger:
# - name: event-logger # Required
# description: "Description of entry" # Optional
#cred:
# - name: "local-cred" # Required
# provider: "localFs" # Required, etcd, consul, localFs, remoteFs are supported options
# description: "Description of entry" # Optional
# locale: "*::*::*::*" # Optional, default: *::*::*::*
# paths: # Optional
# - "example/boot/full/cred.yaml"
#cert:
# - name: "local-cert" # Required
# provider: "localFs" # Required, etcd, consul, localFs, remoteFs are supported options
# description: "Description of entry" # Optional
# locale: "*::*::*::*" # Optional, default: *::*::*::*
# serverCertPath: "example/boot/full/server.pem" # Optional, default: "", path of certificate on local FS
# serverKeyPath: "example/boot/full/server-key.pem" # Optional, default: "", path of certificate on local FS
# clientCertPath: "example/client.pem" # Optional, default: "", path of certificate on local FS
# clientKeyPath: "example/client.pem" # Optional, default: "", path of certificate on local FS
#config:
# - name: rk-main # Required
# path: "example/boot/full/config.yaml" # Required
# locale: "*::*::*::*" # Required, default: *::*::*::*
# description: "Description of entry" # Optional
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
# description: "greeter server" # Optional, default: ""
# cert:
# ref: "local-cert" # Optional, default: "", reference of cert entry declared above
# sw:
# enabled: true # Optional, default: false
# path: "sw" # Optional, default: "sw"
# jsonPath: "" # Optional
# headers: ["sw:rk"] # Optional, default: []
# commonService:
# enabled: true # Optional, default: false
# static:
# enabled: true # Optional, default: false
# path: "/rk/v1/static" # Optional, default: /rk/v1/static
# sourceType: local # Required, options: pkger, local
# sourcePath: "." # Required, full path of source directory
# tv:
# enabled: true # Optional, default: false
# prom:
# enabled: true # Optional, default: false
# path: "" # Optional, default: "metrics"
# pusher:
# enabled: false # Optional, default: false
# jobName: "greeter-pusher" # Required
# remoteAddress: "localhost:9091" # Required
# basicAuth: "user:pass" # Optional, default: ""
# intervalMs: 10000 # Optional, default: 1000
# cert: # Optional
# ref: "local-test" # Optional, default: "", reference of cert entry declared above
# logger:
# zapLogger:
# ref: zap-logger # Optional, default: logger of STDOUT, reference of logger entry declared above
# eventLogger:
# ref: event-logger # Optional, default: logger of STDOUT, reference of logger entry declared above
# interceptors:
# loggingZap:
# enabled: true # Optional, default: false
# zapLoggerEncoding: "json" # Optional, default: "console"
# zapLoggerOutputPaths: ["logs/app.log"] # Optional, default: ["stdout"]
# eventLoggerEncoding: "json" # Optional, default: "console"
# eventLoggerOutputPaths: ["logs/event.log"] # Optional, default: ["stdout"]
# metricsProm:
# enabled: true # Optional, default: false
# auth:
# enabled: true # Optional, default: false
# basic:
# - "user:pass" # Optional, default: []
# ignorePrefix:
# - "/rk/v1" # Optional, default: []
# apiKey:
# - "keys" # Optional, default: []
# meta:
# enabled: true # Optional, default: false
# prefix: "rk" # Optional, default: "rk"
# tracingTelemetry:
# enabled: true # Optional, default: false
# exporter: # Optional, default will create a stdout exporter
# file:
# enabled: true # Optional, default: false
# outputPath: "logs/trace.log" # Optional, default: stdout
# jaeger:
# agent:
# enabled: false # Optional, default: false
# host: "" # Optional, default: localhost
# port: 0 # Optional, default: 6831
# collector:
# enabled: true # Optional, default: false
# endpoint: "" # Optional, default: http://localhost:14268/api/traces
# username: "" # Optional, default: ""
# password: "" # Optional, default: ""
# rateLimit:
# enabled: false # Optional, default: false
# algorithm: "leakyBucket" # Optional, default: "tokenBucket"
# reqPerSec: 100 # Optional, default: 1000000
# paths:
# - path: "/rk/v1/healthy" # Optional, default: ""
# reqPerSec: 0 # Optional, default: 1000000
# jwt:
# enabled: true # Optional, default: false
# signingKey: "my-secret" # Required
# ignorePrefix: # Optional, default: []
# - "/rk/v1/tv"
# - "/sw"
# - "/rk/v1/assets"
# signingKeys: # Optional
# - "key:value"
# signingAlgo: "" # Optional, default: "HS256"
# tokenLookup: "header:<name>" # Optional, default: "header:Authorization"
# authScheme: "Bearer" # Optional, default: "Bearer"
# secure:
# enabled: true # Optional, default: false
# xssProtection: "" # Optional, default: "1; mode=block"
# contentTypeNosniff: "" # Optional, default: nosniff
# xFrameOptions: "" # Optional, default: SAMEORIGIN
# hstsMaxAge: 0 # Optional, default: 0
# hstsExcludeSubdomains: false # Optional, default: false
# hstsPreloadEnabled: false # Optional, default: false
# contentSecurityPolicy: "" # Optional, default: ""
# cspReportOnly: false # Optional, default: false
# referrerPolicy: "" # Optional, default: ""
# ignorePrefix: [] # Optional, default: []
# csrf:
# enabled: true
# tokenLength: 32 # Optional, default: 32
# tokenLookup: "header:X-CSRF-Token" # Optional, default: "header:X-CSRF-Token"
# cookieName: "_csrf" # Optional, default: _csrf
# cookieDomain: "" # Optional, default: ""
# cookiePath: "" # Optional, default: ""
# cookieMaxAge: 86400 # Optional, default: 86400
# cookieHttpOnly: false # Optional, default: false
# cookieSameSite: "default" # Optional, default: "default", options: lax, strict, none, default
# ignorePrefix: [] # Optional, default: []