耶耶耶耶耶 发表于 2023-8-31 04:16:08

Fabric区块链浏览器(2)

本文是区块链浏览器系列的第四篇。
在上一篇文章介绍如何解析区块数据时,使用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"), " ")); 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的情况,上面的代码并不需要进行修改,但对于basicAuth、tokenAuth,上面的代码就需要修改了:
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上查看。
https://img2023.cnblogs.com/blog/1007709/202308/1007709-20230810162948167-1526955652.jpg声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Fabric区块链浏览器(2)