Fabric区块链浏览器(2)

fabric,区块,浏览器 · 浏览次数 : 28

小编点评

**声明:**本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。 **Author:** mengbinblog ** mengbinGithub:** mengbinGithub ** mengbin92cnblogs:** 恋水无意 **恋水无意:** 项目完整代码可以从Github上查看。 **项目内容:** * 实现认证中间件 * 针对 noAuth、basicAuth、tokenAuth 的路由注册 * 对/block/upload、/block/parse/:msgType、/block/update/:channel 接口进行认证

正文

本文是区块链浏览器系列的第四篇。

上一篇文章介绍如何解析区块数据时,使用session对客户端上传的pb文件进行区分,到期后自动删除。

在这片文章中,会着重介绍下认证系统的实现,主要分为三部分:

  • 添加数据库,存储用户信息
  • 实现用户认证中间件
  • 修改路由

1. 用户信息存储

我这里使用MySQL来存储数据,使用gorm来实现与数据库的交换。

首先需要创建用户表:

CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `password` varchar(100) DEFAULT NULL,
  `salt` longtext,
  `created_at` datetime(3) DEFAULT NULL,
  `updated_at` datetime(3) DEFAULT NULL,
  `deleted_at` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

创建MySQL链接句柄:

func InitDB(source string) (*gorm.DB, error) {
	dblog := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			LogLevel:                  logger.Error,
			IgnoreRecordNotFoundError: true,
			Colorful:                  true,
			SlowThreshold:             time.Second,
		},
	)
	return gorm.Open(mysql.Open(source), &gorm.Config{
		SkipDefaultTransaction:                   true,
		AllowGlobalUpdate:                        false,
		DisableForeignKeyConstraintWhenMigrating: true,
		Logger:                                   dblog,
	})
}

表结构比较简单,实现两个查询接口:

func GetUserByName(name string) (*User, error) {
	var user User
	db.Get().First(&user, "name = ?", name)
	if user.ID == 0 {
		return nil, fmt.Errorf("user with name: %s is not found", name)
	}
	return &user, nil
}

func GetUserByID(id uint) (*User, error) {
	var user User
	db.Get().First(&user, "id = ?", id)
	if user.ID == 0 {
		return nil, fmt.Errorf("user with id: %d is not found", id)
	}
	return &user, nil
}

除了查询接口外,还需要提供用户注册,这里直接使用Save()接口进行数据库写入操作:

func RegisterUser(name, password string) (*LoginResponse, error) {
	salt := genSalt()
	u := &User{
		Name:     name,
		Password: utils.CalcPassword(password, salt),
		Salt:     salt,
	}
	if err := db.Get().Save(u).Error; err != nil {
		return nil, errors.Wrap(err, "RegisterUser error")
	}

	now := time.Now()
	claims := &jwtv5.RegisteredClaims{
		ExpiresAt: jwtv5.NewNumericDate(now.Add(30 * time.Minute)),
		Issuer:    "browser",
		Subject:   fmt.Sprintf("%d", u.ID),
	}
	token := jwtv5.NewWithClaims(jwtv5.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(securityKey)
	if err != nil {
		return nil, errors.Wrap(err, "create token error")
	}

	return &LoginResponse{
		Token:    tokenString,
		Expire:   now.Add(30 * time.Minute).Unix(),
		ID:       u.ID,
		Username: u.Name,
	}, nil
}

用户认证采用的JWT(JSON Web Token),实现方法在JWT介绍有介绍,所以还需要提供两个接口:Login实现token获取,RefreshToken刷新token:

func Login(name, password string) (*LoginResponse, error) {
	user, err := GetUserByName(name)
	if err != nil {
		return nil, errors.Wrap(err, "GetUserByName error")
	}

	if utils.CalcPassword(password, user.Salt) != user.Password {
		return nil, errors.New("user name or password is incorrect")
	}

	now := time.Now()
	claims := &jwtv5.RegisteredClaims{
		ExpiresAt: jwtv5.NewNumericDate(now.Add(30 * time.Minute)),
		Issuer:    "browser",
		Subject:   fmt.Sprintf("%d", user.ID),
	}
	token := jwtv5.NewWithClaims(jwtv5.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(securityKey)
	if err != nil {
		return nil, errors.Wrap(err, "create token error")
	}

	return &LoginResponse{
		Token:    tokenString,
		Expire:   now.Add(30 * time.Minute).Unix(),
		ID:       user.ID,
		Username: user.Name,
	}, nil
}

func RefreshToken(id uint) (*LoginResponse, error) {
	user, err := GetUserByID(id)
	if err != nil {
		return nil, errors.Wrap(err, "GetUserByName error")
	}

	now := time.Now()
	claims := &jwtv5.RegisteredClaims{
		ExpiresAt: jwtv5.NewNumericDate(now.Add(30 * time.Minute)),
		Issuer:    "browser",
		Subject:   fmt.Sprintf("%d", user.ID),
	}
	token := jwtv5.NewWithClaims(jwtv5.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(securityKey)
	if err != nil {
		return nil, errors.Wrap(err, "create token error")
	}
	return &LoginResponse{
		Token:    tokenString,
		Expire:   now.Add(30 * time.Minute).Unix(),
		ID:       user.ID,
		Username: user.Name,
	}, nil
}

2. 用户认证中间件

关于Gin中间件的开发,可以参照gin中间件开发,这里增加三种认证方式:noAuth,不使用认证;basicAuth,用户名密码方式认证;tokenAuth,使用token进行认证:

func noAuth(ctx *gin.Context) {
	ctx.Next()
}

func basicAuth(ctx *gin.Context) {
	name, pwd, ok := ctx.Request.BasicAuth()
	if !ok {
		srvLogger.Error("basic auth failed")
		ctx.JSON(http.StatusForbidden, gin.H{"code": http.StatusForbidden, "msg": "basic auth failed"})
		ctx.Abort()
		return
	}
	user, err := data.GetUserByName(name)
	if err != nil {
		srvLogger.Errorf("GetUserByName error: %s", err.Error())
		ctx.JSON(http.StatusForbidden, gin.H{"code": http.StatusForbidden, "msg": err.Error()})
		ctx.Abort()
		return
	}
	if utils.CalcPassword(pwd, user.Salt) != user.Password {
		srvLogger.Error("user name or password is incorrect")
		ctx.JSON(http.StatusForbidden, gin.H{"code": http.StatusForbidden, "msg": "user name or password is incorrect"})
		ctx.Abort()
		return
	}
	ctx.Next()
}

func tokenAuth(ctx *gin.Context) {
	if err := data.ParseJWT(strings.Split(ctx.Request.Header.Get("Authorization"), " ")[1]); err != nil {
		srvLogger.Errorf("tokenAuth error: %s", err.Error())
		ctx.JSON(http.StatusForbidden, gin.H{"code": http.StatusForbidden, "msg": "token auth failed"})
		ctx.Abort()
		return
	}
	ctx.Next()
}

3. 注册路由

上篇中,注册的路由是这样的:

engine.POST("/login", login)
engine.GET("/hi/:name", sayHi)
engine.POST("/block/upload", upload)
engine.GET("/block/parse/:msgType", parse)
engine.POST("/block/update/:channel", updateConfig)

现在需要对/block/upload/block/parse/:msgType/block/update/:channel接口增加认证,这就需要用到我们上面实现的三个中间件。

由于中间件会按照它们的注册顺利来执行,所以需要认证中间件需要在相应的处理接口前执行,针对noAuth的情况,上面的代码并不需要进行修改,但对于basicAuthtokenAuth,上面的代码就需要修改了:

engine.POST("/block/upload", basicAuth, upload)
engine.GET("/block/parse/:msgType", basicAuth, parse)
engine.POST("/block/update/:channel", basicAuth, updateConfig)

engine.POST("/block/upload", tokenAuth, upload)
engine.GET("/block/parse/:msgType", tokenAuth, parse)
engine.POST("/block/update/:channel", tokenAuth, updateConfig)

当然我们也可以使用Handle(httpMethod, relativePath string, handlers ...HandlerFunc)来进行路由注册:

for _, router := range server.Routers() {
	var handlers []gin.HandlerFunc
	if router.AuthType == 0 {
		router.AuthType = conf.AuthType
	}
	switch router.AuthType {
	case config.Server_BASICAUTH:
		handlers = append(handlers, basicAuth)
	case config.Server_TOKENAUTH:
		handlers = append(handlers, tokenAuth)
	default:
		handlers = append(handlers, noAuth)
	}
	handlers = append(handlers, router.Handler)
	engine.Handle(router.Method, router.Path, handlers...)
}

项目完整代码可以从Github上查看。


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


与Fabric区块链浏览器(2)相似的内容:

Fabric区块链浏览器(2)

本文是区块链浏览器系列的第四篇。 在[上一篇文章](https://mengbin.top/2023-08-13-blockBrowser/)介绍如何解析区块数据时,使用`session`对客户端上传的pb文件进行区分,到期后自动删除。 在这片文章中,会着重介绍下认证系统的实现,主要分为三部分: -

从源码中解析fabric区块数据结构(一)

从源码中解析fabric区块数据结构(一) 前言 最近打算基于fabric-sdk-go实现hyperledger fabric浏览器,其中最重要的一步就是解析fabric的上链区块。虽说fabric是Golang实现的,但直到2021年2月1号才发布了第一个稳定版fabric-sdk-go,而且官

Fabric区块链浏览器(1)

本文是区块链浏览器系列的第三篇,本文介绍区块链浏览器的主体部分,即区块数据的解析。 这一版本的[区块链浏览器](https://github.com/mengbin92/browser/tree/gin)是基于[gin](https://github.com/gin-gonic/gin)实现的,只提

Fabric区块链浏览器(3)

本文是区块链浏览器系列的第五篇,项目完整代码在[这里](https://github.com/mengbin92/browser/tree/main)。 在[上一篇文章](https://mengbin.top/2023-08-20-browser2/)中给浏览器增加了简单的用户认证,至此浏览器的基

Fabric配置块结构解析

本文是区块链浏览器系列的第二篇。 上一篇介绍了交易块中的数据结构,这一篇介绍区块链网络中的配置块数据结构。 这两种区块中数据结构内容的区别主要Payload结构体中的Data域中的内容,接下来将以类图的形式来解析Data域包含的信息: classDiagram class Payload{ Head

基于密码学的身份混淆系统 -- idmix

## 简介 Hyperledger Fabric的Idemix是一个基于密码学的身份混淆系统,它提供了一种在区块链网络中实现用户隐私的方法。Idemix的主要特性是它的零知识证明系统,这是一种允许用户证明他们拥有某些属性,而无需透露任何其他信息的技术。 以下是一些更详细的关于Idemix的信息: 1

Fabric 2.x 智能合约开发记录

表象:Return schema invalid. required items must be unique [recovered] 虽然 Fabric v2.2 已经发布了很久了,但之前因为项目历史问题,一直使用的都是 Fabric v1.4.8,所以智能合约也一直使用的都是 github.co

【Azure Service Fabric】关于Service Fabric的相关问题

问题一:Service Fabric 是否支持Private Link? 在Azure Private Endpoint文档中,罗列出了 Azure 上支持 Private Link 的服务。Service Fabric不在其中。 Azure Private Link availability :h

Hyperledger Fabric系统链码介绍

在Hyperledger Fabric中,LSCC(Lifecycle System Chaincode)、CSCC(Chaincode System Chaincode)、QSCC(Query System Chaincode)、ESCC(Endorsement System Chaincode)

【Azure Fabric Service】Service Fabric 托管群集通过 Connect-ServiceFabricCluster 连接时候报错 CertificatedNotMatched

问题描述 Service Fabric 托管群集, 使用Key Vault中证书,把证书导入到本地安装后,使用该证书的 Thumbprint 作为指令 Connect-ServiceFabricCluster 的 ServerCertThumbprint 和FindValue 的值。结果连接失败,错