GoFrame framework: Basic Auth Middleware

introduce

Through a complete example, in gogf/gf Add Basic Auth middleware to microservices.

What is HTTP Basic Auth middleware? Basic Auth middleware will intercept each API request and verify the verification of Basic Auth or X-API-Key.

We will use rk-boot To start gogf/gf Microservices.

rk-boot Is a framework that can start a variety of Web services through YAML. Please refer to the last chapter of this article for details rk-boot Details.

Please visit the following address for a complete tutorial: https://rkdocs.netlify.app/cn

install

go get github.com/rookie-ninja/rk-boot/gf

Quick start

1. Create boot yaml

boot. The yaml file describes gogf/gf Meta information required for framework startup.

To verify, we launched the following options:

  • commonService: commonService contains a series of general API s. details
  • interceptors.auth: Basic Auth middleware. The default user name 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

Add / v1/greeter API.

If you want to add Swagger validation options to your API, 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. Verification

Without Basic Auth, we get 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

Basic Auth is provided. For security reasons, 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!"}

Use X-API-Key authorization

1. Modify boot yaml

In this step, we start X-API-Key, and the value of the 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. Verification

In the same case, we get 401 error code without providing X-API-Key.

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 to make the middleware ignore and verify 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 Generate the config file required by Swagger UI, so add some comments to the code.

  • Add the following annotation in 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 the 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 that can start a variety of Web services through YAML.

It is similar to Spring boot. By integrating rk XXX series libraries, you can start a variety of Web frameworks. Of course, users can also customize the RK XXX library and integrate it into rk boot.

Rk boot highlights

Launch different Web frameworks through YAML files in the same format.

For example, we can start grpc, gin, echo and goframe frameworks in one process through the following files. Unify the micro service layout within the team.

  • Dependent 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())
}
  • verification
# 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 framework supported by rk boot

Welcome to contribute a new Web framework to the RK boot series.

reference resources docs & rk-gin As an example.

frame

Development status

install

rely on

Stable

go get github.com/rookie-ninja/rk-boot/gin

Stable

go get github.com/rookie-ninja/rk-boot/grpc

Stable

go get github.com/rookie-ninja/rk-boot/echo

Stable

go get github.com/rookie-ninja/rk-boot/gf

Testing

go get github.com/rookie-ninja/rk-boot/fiber

Testing

go get github.com/rookie-ninja/rk-boot/zero

Testing

go get github.com/rookie-ninja/rk-boot/mux

Rk GF introduction

rk-gf Used to launch via YAML gogf/gf Web services.

Supported features

According to the YAML file initialization example below, if it is external, the native usage will be maintained.

example

introduce

ghttp.Server

Primordial gogf/gf

Config

Primordial spf13/viper Parameter instance

Logger

Primordial uber-go/zap Log instance

EventLogger

Used to record RPC request logs, using rk-query

Credential

Used to pull Credential from a remote service, such as ETCD

Cert

Obtain the TLS/SSL certificate from the remote service (ETCD, etc.) and start SSL/TLS

Prometheus

Start the Prometheus client and push it to as needed pushgateway

Swagger

Launch Swagger UI locally

CommonService

Expose common API s

TV

TV web page, showing the basic information of micro services

StaticFileHandler

Start the static file download service in the form of Web, and 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 Record 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 the service meta information and add it to the return Header

Auth

Middleware supporting basic auth & API key authentication

RateLimit

RPC speed limiting Middleware

Timeout

RPC timeout Middleware

CORS

CORS Middleware

JWT

JWT validation

Secure

Server side security middleware

CSRF

CSRF Middleware

GoFrame complete 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: []

Keywords: Go Microservices

Added by GrizzlyBear on Tue, 04 Jan 2022 15:44:40 +0200