前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Fabric区块链浏览器(2):用户认证

Fabric区块链浏览器(2):用户认证

作者头像
孟斯特
发布2023-10-16 20:01:33
1550
发布2023-10-16 20:01:33
举报
文章被收录于专栏:code人生code人生

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

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

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

•添加数据库,存储用户信息

•实现用户认证中间件

•修改路由

1. 用户信息存储

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

首先需要创建用户表:

代码语言:javascript
复制
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链接句柄:

代码语言:javascript
复制
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,
    })
}

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

代码语言:javascript
复制
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()接口进行数据库写入操作:

代码语言:javascript
复制
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介绍[2]有介绍,所以还需要提供两个接口:Login实现token获取,RefreshToken刷新token:

代码语言:javascript
复制
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中间件开发[3],这里增加三种认证方式:noAuth,不使用认证;basicAuth,用户名密码方式认证;tokenAuth,使用token进行认证:

代码语言:javascript
复制
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. 注册路由

在上篇[4]中,注册的路由是这样的:

代码语言:javascript
复制
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,上面的代码就需要修改了:

代码语言:javascript
复制
engine.POST("/block/upload", basicAuth, upload)
engine.GET("/block/parse/:msgType", basicAuth, parse)
engine.POST("/block/update/:channel", basicAuth, updateConfig)

代码语言:javascript
复制
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)来进行路由注册:

代码语言:javascript
复制
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[5]上查看。

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

References

[1] 上一篇文章: https://mengbin.top/2023-08-13-blockBrowser/ [2] JWT介绍: https://mengbin.top/2023-08-17-jwt/ [3] gin中间件开发: https://mengbin.top/2023-08-10-middleware/ [4] 上篇: https://mengbin.top/2023-08-13-blockBrowser/ [5] Github: https://github.com/mengbin92/browser/tree/gin [6] 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0): https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh [7] mengbin: mengbin1992@outlook.com [8] mengbin: https://mengbin.top [9] mengbin92: https://mengbin92.github.io/ [10] 恋水无意: https://www.cnblogs.com/lianshuiwuyi/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟斯特 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 用户信息存储
  • 2. 用户认证中间件
  • 3. 注册路由
    • References
    相关产品与服务
    区块链
    云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档