mirror of
https://github.com/kataras/iris.git
synced 2026-01-09 13:05:56 +00:00
more features and fix database/mysql:jwt example
This commit is contained in:
@@ -196,7 +196,11 @@
|
||||
* [Ttemplates and Functions](i18n/template)
|
||||
* [Pluralization and Variables](i18n/plurals)
|
||||
* Authentication, Authorization & Bot Detection
|
||||
* [Basic Authentication](auth/basicauth/main.go)
|
||||
* Basic Authentication
|
||||
* [Basic](auth/basicauth/basic)
|
||||
* [Load from a slice of Users](auth/basicauth/users_list)
|
||||
* [Load from a file & encrypted passwords](auth/basicauth/users_file_bcrypt)
|
||||
* [Fetch & validate a User from a Database (MySQL)](auth/basicauth/database)
|
||||
* [CORS](auth/cors)
|
||||
* JSON Web Tokens
|
||||
* [Basic](auth/jwt/basic/main.go)
|
||||
|
||||
@@ -44,12 +44,12 @@ func newApp() *iris.Application {
|
||||
needAuth := app.Party("/admin", auth)
|
||||
{
|
||||
//http://localhost:8080/admin
|
||||
needAuth.Get("/", h)
|
||||
needAuth.Get("/", handler)
|
||||
// http://localhost:8080/admin/profile
|
||||
needAuth.Get("/profile", h)
|
||||
needAuth.Get("/profile", handler)
|
||||
|
||||
// http://localhost:8080/admin/settings
|
||||
needAuth.Get("/settings", h)
|
||||
needAuth.Get("/settings", handler)
|
||||
|
||||
needAuth.Get("/logout", logout)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func main() {
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func h(ctx iris.Context) {
|
||||
func handler(ctx iris.Context) {
|
||||
// username, password, _ := ctx.Request().BasicAuth()
|
||||
// third parameter it will be always true because the middleware
|
||||
// makes sure for that, otherwise this handler will not be executed.
|
||||
@@ -25,5 +25,5 @@ func TestBasicAuth(t *testing.T) {
|
||||
|
||||
// with invalid basic auth
|
||||
e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword").
|
||||
Expect().Status(httptest.StatusForbidden)
|
||||
Expect().Status(httptest.StatusUnauthorized)
|
||||
}
|
||||
18
_examples/auth/basicauth/database/Dockerfile
Normal file
18
_examples/auth/basicauth/database/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# docker build -t myapp .
|
||||
# docker run --rm -it -p 8080:8080 myapp:latest
|
||||
FROM golang:latest AS builder
|
||||
RUN apt-get update
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64
|
||||
WORKDIR /go/src/app
|
||||
COPY go.mod .
|
||||
RUN go mod download
|
||||
# cache step
|
||||
COPY . .
|
||||
RUN go install
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /go/bin/myapp .
|
||||
ENTRYPOINT ["./myapp"]
|
||||
44
_examples/auth/basicauth/database/README.md
Normal file
44
_examples/auth/basicauth/database/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# BasicAuth + MySQL & Docker Example
|
||||
|
||||
## ⚡ Get Started
|
||||
|
||||
Download the folder.
|
||||
|
||||
### Install (Docker)
|
||||
|
||||
Install [Docker](https://www.docker.com/) and execute the command below
|
||||
|
||||
```sh
|
||||
$ docker-compose up --build
|
||||
```
|
||||
|
||||
### Install (Manually)
|
||||
|
||||
Run `go build` or `go run main.go` and read below.
|
||||
|
||||
#### MySQL
|
||||
|
||||
Environment variables:
|
||||
|
||||
```sh
|
||||
MYSQL_USER=user_myapp
|
||||
MYSQL_PASSWORD=dbpassword
|
||||
MYSQL_HOST=localhost
|
||||
MYSQL_DATABASE=myapp
|
||||
```
|
||||
|
||||
Download the schema from [migration/db.sql](migration/db.sql) and execute it against your MySQL server instance.
|
||||
|
||||
<http://localhost:8080>
|
||||
|
||||
```sh
|
||||
username: admin
|
||||
password: admin
|
||||
```
|
||||
|
||||
```sh
|
||||
username: iris
|
||||
password: iris_password
|
||||
```
|
||||
|
||||
The example does not contain code to add a user to the database, as this is out of the scope of this middleware. More features can be implemented by end-developers.
|
||||
32
_examples/auth/basicauth/database/docker-compose.yml
Normal file
32
_examples/auth/basicauth/database/docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mysql
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: dbpassword
|
||||
MYSQL_DATABASE: myapp
|
||||
MYSQL_USER: user_myapp
|
||||
MYSQL_PASSWORD: dbpassword
|
||||
tty: true
|
||||
volumes:
|
||||
- ./migration:/docker-entrypoint-initdb.d
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
PORT: 8080
|
||||
MYSQL_USER: user_myapp
|
||||
MYSQL_PASSWORD: dbpassword
|
||||
MYSQL_DATABASE: myapp
|
||||
MYSQL_HOST: db
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "tcp://db:3306"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
depends_on:
|
||||
- db
|
||||
8
_examples/auth/basicauth/database/go.mod
Normal file
8
_examples/auth/basicauth/database/go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module myapp
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/kataras/iris/v12 master
|
||||
)
|
||||
113
_examples/auth/basicauth/database/main.go
Normal file
113
_examples/auth/basicauth/database/main.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/middleware/basicauth"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // lint: mysql driver.
|
||||
)
|
||||
|
||||
// User is just an example structure of a user,
|
||||
// it MUST contain a Username and Password exported fields
|
||||
// or/and complete the basicauth.User interface.
|
||||
type User struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
Username string `db:"username" json:"username"`
|
||||
Password string `db:"password" json:"password"`
|
||||
Email string `db:"email" json:"email"`
|
||||
}
|
||||
|
||||
// GetUsername returns the Username field.
|
||||
func (u User) GetUsername() string {
|
||||
return u.Username
|
||||
}
|
||||
|
||||
// GetPassword returns the Password field.
|
||||
func (u User) GetPassword() string {
|
||||
return u.Password
|
||||
}
|
||||
|
||||
func main() {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
||||
getenv("MYSQL_USER", "user_myapp"),
|
||||
getenv("MYSQL_PASSWORD", "dbpassword"),
|
||||
getenv("MYSQL_HOST", "localhost"),
|
||||
getenv("MYSQL_DATABASE", "myapp"),
|
||||
)
|
||||
db, err := connect(dsn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Validate a user from database.
|
||||
allowFunc := func(ctx iris.Context, username, password string) (interface{}, bool) {
|
||||
user, err := db.getUserByUsernameAndPassword(context.Background(), username, password)
|
||||
return user, err == nil
|
||||
}
|
||||
|
||||
opts := basicauth.Options{
|
||||
Realm: basicauth.DefaultRealm,
|
||||
ErrorHandler: basicauth.DefaultErrorHandler,
|
||||
Allow: allowFunc,
|
||||
}
|
||||
|
||||
auth := basicauth.New(opts)
|
||||
|
||||
app := iris.New()
|
||||
app.Use(auth)
|
||||
app.Get("/", index)
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
user := ctx.User()
|
||||
ctx.JSON(user)
|
||||
}
|
||||
|
||||
func getenv(key string, def string) string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
type database struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
func connect(dsn string) (*database, error) {
|
||||
conn, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.Ping()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &database{conn}, nil
|
||||
}
|
||||
|
||||
func (db *database) getUserByUsernameAndPassword(ctx context.Context, username, password string) (User, error) {
|
||||
query := fmt.Sprintf("SELECT * FROM %s WHERE %s = ? AND %s = ? LIMIT 1", "users", "username", "password")
|
||||
rows, err := db.QueryContext(ctx, query, username, password)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return User{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
var user User
|
||||
err = rows.Scan(&user.ID, &user.Username, &user.Password, &user.Email)
|
||||
return user, err
|
||||
}
|
||||
22
_examples/auth/basicauth/database/migration/db.sql
Normal file
22
_examples/auth/basicauth/database/migration/db.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE myapp;
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS users;
|
||||
CREATE TABLE users (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
username varchar(255) NOT NULL,
|
||||
password varchar(255) NOT NULL,
|
||||
email varchar(255) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
INSERT INTO users (username,password,email)
|
||||
VALUES
|
||||
('admin', 'admin', 'kataras2006@hotmail.com'),
|
||||
("iris", 'iris_password', 'iris-go@outlook.com');
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
30
_examples/auth/basicauth/users_file_bcrypt/main.go
Normal file
30
_examples/auth/basicauth/users_file_bcrypt/main.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/middleware/basicauth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
auth := basicauth.Load("users.yml", basicauth.BCRYPT)
|
||||
/* Same as:
|
||||
opts := basicauth.Options{
|
||||
Realm: basicauth.DefaultRealm,
|
||||
Allow: basicauth.AllowUsersFile("users.yml", basicauth.BCRYPT),
|
||||
}
|
||||
|
||||
auth := basicauth.New(opts)
|
||||
*/
|
||||
|
||||
app := iris.New()
|
||||
app.Use(auth)
|
||||
app.Get("/", index)
|
||||
// kataras:kataras_pass
|
||||
// makis:makis_pass
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
user := ctx.User()
|
||||
ctx.JSON(user)
|
||||
}
|
||||
12
_examples/auth/basicauth/users_file_bcrypt/users.yml
Normal file
12
_examples/auth/basicauth/users_file_bcrypt/users.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# The file cannot be modified during the serve time.
|
||||
# To support real-time users changes please use the Options.Allow instead,
|
||||
# (see the database example for that).
|
||||
#
|
||||
# Again, the username and password (or capitalized) fields are required,
|
||||
# the rest are optional, depending on your application needs.
|
||||
- username: kataras
|
||||
password: $2a$10$Irg8k8HWkDlvL0YDBKLCYee6j6zzIFTplJcvZYKA.B8/clHPZn2Ey # encrypted of kataras_pass
|
||||
age: 27
|
||||
role: admin
|
||||
- username: makis
|
||||
password: $2a$10$3GXzp3J5GhHThGisbpvpZuftbmzPivDMo94XPnkTnDe7254x7sJ3O # encrypted of makis_pass
|
||||
58
_examples/auth/basicauth/users_list/main.go
Normal file
58
_examples/auth/basicauth/users_list/main.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/middleware/basicauth"
|
||||
)
|
||||
|
||||
// User is just an example structure of a user,
|
||||
// it MUST contain a Username and Password exported fields
|
||||
// or complete the basicauth.User interface.
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
var users = []User{
|
||||
{"admin", "admin", []string{"admin"}},
|
||||
{"kataras", "kataras_pass", []string{"manager", "author"}},
|
||||
{"george", "george_pass", []string{"member"}},
|
||||
{"john", "john_pass", []string{}},
|
||||
}
|
||||
|
||||
func main() {
|
||||
opts := basicauth.Options{
|
||||
Realm: basicauth.DefaultRealm,
|
||||
// Defaults to 0, no expiration.
|
||||
// Prompt for new credentials on a client's request
|
||||
// made after 10 minutes the user has logged in:
|
||||
MaxAge: 10 * time.Minute,
|
||||
// Clear any expired users from the memory every one hour,
|
||||
// note that the user's expiration time will be
|
||||
// reseted on the next valid request (when Allow passed).
|
||||
GC: basicauth.GC{
|
||||
Every: 2 * time.Hour,
|
||||
},
|
||||
// The users can be a slice of custom users structure
|
||||
// or a map[string]string (username:password)
|
||||
// or []map[string]interface{} with username and passwords required fields,
|
||||
// read the godocs for more.
|
||||
Allow: basicauth.AllowUsers(users),
|
||||
}
|
||||
|
||||
auth := basicauth.New(opts)
|
||||
// OR: basicauth.Default(users)
|
||||
|
||||
app := iris.New()
|
||||
app.Use(auth)
|
||||
app.Get("/", index)
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
user, _ := ctx.User().GetRaw()
|
||||
ctx.JSON(user)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ MYSQL_HOST=localhost
|
||||
MYSQL_DATABASE=myapp
|
||||
```
|
||||
|
||||
Download the schema from [migration/myapp.sql](migration/myapp.sql) and execute it against your MySQL server instance.
|
||||
Download the schema from [migration/db.sql](migration/db.sql) and execute it against your MySQL server instance.
|
||||
|
||||
```sql
|
||||
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@@ -139,7 +139,7 @@ Testing is important. The code is written in a way that testing should be trivia
|
||||
|
||||
## Packages
|
||||
|
||||
- https://github.com/dgrijalva/jwt-go (JWT parsing)
|
||||
- https://github.com/kataras/jwt (JWT parsing)
|
||||
- https://github.com/go-sql-driver/mysql (Go Driver for MySQL)
|
||||
- https://github.com/DATA-DOG/go-sqlmock (Testing DB see [service/category_service_test.go](service/category_service_test.go))
|
||||
- https://github.com/kataras/iris (HTTP)
|
||||
|
||||
@@ -15,18 +15,19 @@ import (
|
||||
// Router accepts any required dependencies and returns the main server's handler.
|
||||
func Router(db sql.Database, secret string) func(iris.Party) {
|
||||
return func(r iris.Party) {
|
||||
j := jwt.HMAC(15*time.Minute, secret)
|
||||
|
||||
r.Use(requestid.New())
|
||||
r.Use(verifyToken(j))
|
||||
|
||||
signer := jwt.NewSigner(jwt.HS256, secret, 15*time.Minute)
|
||||
r.Get("/token", writeToken(signer))
|
||||
|
||||
verify := jwt.NewVerifier(jwt.HS256, secret).Verify(nil)
|
||||
r.Use(verify)
|
||||
// Generate a token for testing by navigating to
|
||||
// http://localhost:8080/token endpoint.
|
||||
// Copy-paste it to a ?token=$token url parameter or
|
||||
// open postman and put an Authentication: Bearer $token to get
|
||||
// access on create, update and delete endpoinds.
|
||||
|
||||
r.Get("/token", writeToken(j))
|
||||
|
||||
var (
|
||||
categoryService = service.NewCategoryService(db)
|
||||
productService = service.NewProductService(db)
|
||||
@@ -73,25 +74,19 @@ func Router(db sql.Database, secret string) func(iris.Party) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeToken(j *jwt.JWT) iris.Handler {
|
||||
func writeToken(signer *jwt.Signer) iris.Handler {
|
||||
return func(ctx iris.Context) {
|
||||
claims := jwt.Claims{
|
||||
Issuer: "https://iris-go.com",
|
||||
Audience: jwt.Audience{requestid.Get(ctx)},
|
||||
Audience: []string{requestid.Get(ctx)},
|
||||
}
|
||||
|
||||
j.WriteToken(ctx, claims)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyToken(j *jwt.JWT) iris.Handler {
|
||||
return func(ctx iris.Context) {
|
||||
// Allow all GET.
|
||||
if ctx.Method() == iris.MethodGet {
|
||||
ctx.Next()
|
||||
token, err := signer.Sign(claims)
|
||||
if err != nil {
|
||||
ctx.StopWithStatus(iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
j.Verify(ctx)
|
||||
ctx.Write(token)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/kataras/iris/v12 master
|
||||
github.com/kataras/iris/v12 v12.2.0-alpha.0.20201117050536-962ffd67721a
|
||||
github.com/mailgun/groupcache/v2 v2.1.0
|
||||
)
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
// $ go build .
|
||||
|
||||
func main() {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
||||
getenv("MYSQL_USER", "user_myapp"),
|
||||
|
||||
Reference in New Issue
Block a user