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

上一篇文章介绍如何解析区块数据时,使用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)的更多相关文章

  1. 第五章 FISCO BCOS 区块链浏览器的部署

    想了解相关区块链开发,技术提问,请加QQ群:538327407 前提 前面我们已经通过底层部署.sdk调测.自定义智能合约编写与部署.联合单元测试调测,已经初步对FISCO BCOS的区块链底层和实际 ...

  2. hyperledger explorer 结合 fabric1.4 搭建 区块链浏览器 踩坑记录

    博主通过这篇博客的步骤搭建区块链浏览器:https://blog.csdn.net/qq_32675427/article/details/99946945 进行到下面这一步时出现各种异常,浪费了博主 ...

  3. github源码开源区块链浏览器

    <ignore_js_op> 帅爆了吧 https://blockexplorer.com/ github源码:https://github.com/bitcoin-blockexplor ...

  4. 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1

    摘要: 全球开源区块链领域影响最为广泛的Hyperledger Fabric日前宣布了1.1版本的正式发布,带来了一系列丰富的新功能以及在安全性.性能与扩展性等方面的显著提升.阿里云容器服务区块链解决 ...

  5. 区块链学习7:超级账本项目Hyperledger与Fabric以及二者的关系

    ☞ ░ 前往老猿Python博文目录 ░ 一.超级账本(hyperledger) 超级账本(hyperledger)是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目,成员包括 ...

  6. 区块链学习7:超级账本项目Fabric中的背书、背书节点、背书策略、背书签名

    ☞ ░ 前往老猿Python博文目录 ░ 在Hyperledger Fabric区块链中,有背书节点进行背书,Hyperledger Fabric 使用背书策略来定义哪些节点需要执行交易. Hyper ...

  7. Hyperledger Fabric 2.x Java区块链应用

    一.说明 在上一篇文章中 <Hyperledger Fabric 2.x 自定义智能合约> 分享了智能合约的安装并使用 cli 客户端进行合约的调用:本文将使用 Java 代码基于 fab ...

  8. 如何从零开始学习区块链技术——推荐从以太坊开发DApp开始

    很多人迷惑于区块链和以太坊,不知如何学习,本文简单说了一下学习的一些方法和资源. 一. 以太坊和区块链的关系 从区块链历史上来说,先诞生了比特币,当时并没有区块链这个技术和名词,然后业界从比特币中提取 ...

  9. NEO区块链-DAPP开发直通车-第零篇

    什么是DAPP DAPP 是以太坊发明的词汇 Decentralized Application. 目前基于区块链技术开发的应用程序广泛的接受使用了这一名称.   NEL将为开发DAPP提供全面的服务 ...

  10. [币严区块链]BitcoinCash - BCH钱包地址生成与扫块充值监控(JAVA版)

    本文的方案无需自建节点,因为BCH当前区块数据大小已经达到200G以上,BTC区块数据也已超过300G,若每个币都自建节点,对云服务器的消耗会非常大. 认识BitcoinCash(BCH) Bitco ...

随机推荐

  1. 什么是 A/B 实验,为什么要开 A/B 实验?

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 1.什么是 A/B 实验 A/B 实验也被称为 A/B 测试,实验的基本思路是在线上流量中取出一小部分(较低风险) ...

  2. 用 Python 开发了一个 PDF 抽取Excel表格的小工具

    大家好哇 从 PDF 里 copy 表格时,粘贴出来后格式都是错乱的.这麻烦事交给 Python 再合适不过里,我开发了一个从 PDF 抽取表格另存为 Excel 文件的应用,我把它部到 huggin ...

  3. 【vue3】创建vue3项目的两种方式 setup函数 ref和reactive 计算和监听属性 生命周期 hooks toRefs script setup的作用和lang=ts 后台管理模板

    目录 昨日回顾 今日内容 1 vue3介绍 2 创建vue3项目的两种方式 vue-cli创建Vue3项目 Vite创建Vue3项目 Vue3目录结构 Vite的优点 3 setup函数 Vue3创建 ...

  4. ORM执行SQL 双下划线查询 ORM外键字段创建 外键字段相关操作 ORM跨表查询 跨表查询进阶操作

    目录 ORM执行SQL语句 方式1:使用pymysql模块 方式2:使用raw方法 方式3:django connection 双下划线查询 __gt(>) __lt(<) queryse ...

  5. 《Function Programming in C++》

    说明 <Functional Programming in C++>书中代码练习测试以及一些笔记,部分代码需要用到C++20可以使用在线编译器编译代码 地址:https://coliru. ...

  6. vue tabBar导航栏设计实现2-抽取tab-bar

    系列导航 一.vue tabBar导航栏设计实现1-初步设计 二.vue tabBar导航栏设计实现2-抽取tab-bar 三.vue tabBar导航栏设计实现3-进一步抽取tab-item 四.v ...

  7. C#设计模式09——组合模式的写法

    1. 什么是C#组合模式? 组合模式是一种结构型设计模式,它允许将对象组合成树形结构以表示"整体/部分"层次结构.使用此模式,客户端可以按相同的方式处理单个对象和对象集合,而不必关 ...

  8. df -h与df -i的区别

    一. df命令详解: linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况.可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息 -a 全部文件系统列表 -h ...

  9. 机器学习-线性分类-支持向量机SVM-SMO算法代码实现-15

    1. alpha2 的修剪 if y1 != y2 : α1 - α2 = k # 不用算k的具体大小 if k > 0: # 上图的左 下这条线 α2 的区间 (0, c-k) k < ...

  10. wireshark 显示过滤表达式

    转载请注明出处: 1.根据协议过滤: 在显示过滤表达式的输入框中直接输入对应的协议类型即可:http   tcp  udp 2.根据 IP 过滤: 根据源IP地址过滤:如源地址IP为:127.0.0. ...