如何将我的服务开放给用户:构建 API 接口和用户认证的实践指南 | 青训营

184 阅读8分钟

在逐步学习完青训营的课程后,我了解到了Go中web服务的创建和使用,最开始只是利用自带的 net/http 模块实现了一些简单的web服务,后面在准备后端大项目的时候,开始接触到了Go的框架—gin,今天我们就来说一说如何通过gin框架来创建我们web访问,并且如何开放相应的接口给用户,了解什么是 RESTful API 风格,在给用户提供服务的同时,如何对访问的用户进行认证。

一、什么是 API

1、API 概念

API(Application Programming Interface,应用程序接口)是指两个不同软件应用之间进行交互的一组方法。它是现代软件开发中不可或缺的一部分,让不同的应用程序能够相互通信、共享数据,并且以一种有序的方式进行整合。

2、API 的作用

可以连接不同的软件和服务,以确保它们可以无缝地集成和运行。通过提供一个标准的接口,API允许开发人员创建具有不同功能和架构的应用程序之间的关联性。API的主要工作是将特定应用程序的功能和数据集合封装在一起,以使其他应用程序能够轻松地使用它们。

3、API 的优点

可以大量减少代码的冗余性,加强了软件的重用性,同时还可以更好的支持分布式系统和微服务架构。

4、API的类型

API 可以被归为三类:Web API、操作系统API、以及应用程序API(今天我们主要说的是Web API)

二、RESTful API 介绍

1、基本概念

RESTful API(Representational State Transferful Application Programming Interface)是一种设计和构建网络应用程序的架构风格,用于处理资源的访问和操作。它遵循一组约定和规则,使得客户端和服务器之间的通信变得简单、灵活和可伸缩。

2、设计原则和规范

  • 资源:资源就是网络上的一个实体,一段文本,一张图片或者一首歌曲。资源总是要通过一种载体来反应它的内容。JSON是现在最常用的资源表现形式。
  • 统一接口:RESTful风格的数据元操CRUD(create,read,update,delete)分别对应HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口。
  • URI:可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。一般的,每个资源至少有一个URI与之对应,最典型的URI就是URL。
  • 无状态:所谓无状态即所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。

通过遵循这些原则和规范,RESTful API可以提供灵活、可扩展和易于理解的接口,使客户端和服务器之间的通信变得更加简单和高效。

三、了解和使用gin框架构建 API 接口

1、Gin框架介绍

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

Gin 包括以下几个主要的部分:

  • 设计精巧的路由/中间件系统;
  • 简单好用的核心上下文Context;
  • 附赠工具集(JSON/XML 响应, 数据绑定与校验等)

2、gin 包安装

go get -u github.com/gin-gonic/gin

3、创建第一个接口

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000") 
}

这个时候我们运行代码,然后打开我们的浏览器打开地址:http://127.0.0.1:8000/ 即可访问到我们刚才创建的一个api 路径是 / ,页面上会显示一个 Hello World 文字

简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。

4、路由介绍

基本路由 gin 框架中采用的路由库是 httprouter

// 创建带有默认中间件的路由:
// 日志与恢复中间件
router := gin.Default()
//创建不带中间件的路由:
//r := gin.New()
​
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)

四、如何做到用户认证

1、什么是用户认证

简单来说用户认证就是判断一个用户是否为合法用户的过程。

2、常见的用户认证方式

  1. 用户名和密码认证:这是最常见的认证方式,用户需要提供其用户名和密码来验证身份。通常使用表单提交或基本身份验证(HTTP Basic Authentication)进行传输。
  2. OAuth 认证:OAuth 是一种开放标准的认证协议,通过授权服务器和资源服务器的交互,允许用户使用第三方应用程序(如社交媒体平台)进行认证,而无需共享其用户名和密码。
  3. OpenID Connect 认证:OpenID Connect 是建立在 OAuth 2.0 基础上的认证协议,它允许用户使用现有的身份提供者(如 Google、Microsoft)进行认证,并从认证服务器获取身份令牌。
  4. SAML 认证:Security Assertion Markup Language(SAML)是一种基于 XML 的认证标准,允许不同域之间的单点登录(Single Sign-On)和授权。
  5. JWT 认证:JSON Web Token(JWT)是一种轻量级的安全令牌,可以在用户和服务之间安全地传输信息。通常用于无状态的身份验证和授权。
  6. 双因素认证:双因素认证要求用户提供两个或多个验证因素,例如密码和手机验证码、指纹识别等。这增加了系统的安全性,防止未经授权的访问。

目前在web方面常用的是 jwt 认证,在go语言中,官方也提供了 jwt-go 包来处理JWT令牌。

JWT 通常由三个部分组成:

  1. 头部(Header):JWT 的头部通常由两部分组成,即令牌类型(typ)和签名算法(alg)信息。这些信息以 JSON 对象的形式进行编码,并使用 Base64 编码进行传输。
  2. 载荷(Payload):载荷包含有关 JWT 的声明和其他相关数据。它可以包含标准声明(如发行人、到期时间、主题等)和自定义声明。载荷也被编码为 JSON 对象,并使用 Base64 编码进行传输。
  3. 签名(Signature):签名用于验证令牌的完整性和真实性。签名通常由头部、载荷、密钥和指定的签名算法生成。通过对头部和载荷进行哈希计算并与签名进行比较,可以确定令牌是否篡改过。

3、jwt-go 使用

(1)安装jwt-go 和 bcrypt
go get -u github.com/dgrijalva/jwt-go
go get -u golang.org/x/crypto/bcrypt
(2)使用 jwt 创建一个简单令牌认证中间件

在下面代码中,当客户端访问 "/login" 路由时,将生成一个 JWT 令牌并返回给客户端。然后,当客户端访问 "/protected" 路由时,需要在请求头中提供该 JWT 令牌进行认证。

package main
​
import (
    "fmt"
    "log"
    "net/http""github.com/dgrijalva/jwt-go"
)
​
func main() {
    http.HandleFunc("/login", loginHandler)
    http.HandleFunc("/protected", protectedHandler)
​
    // 启动服务器
    log.Fatal(http.ListenAndServe(":8080", nil))
}
​
// 登录处理程序,生成令牌
func loginHandler(w http.ResponseWriter, r *http.Request) {
    // 在此处进行身份验证(例如,检查用户名和密码)// 创建令牌声明
    claims := jwt.MapClaims{
        "username": "john.doe",
    }
​
    // 创建令牌对象
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
​
    // 使用密钥对令牌进行签名
    secretKey := []byte("keytest")
    signedToken, err := token.SignedString(secretKey)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Failed to create token")
        return
    }
​
    // 将令牌发送给客户端
    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprintf(w, signedToken)
}
​
// 受保护的处理程序,需要令牌进行访问
func protectedHandler(w http.ResponseWriter, r *http.Request) {
    // 获取请求头中的令牌
    authHeader := r.Header.Get("Authorization")
    if authHeader == "" {
        w.WriteHeader(http.StatusUnauthorized)
        fmt.Fprintf(w, "Missing authorization header")
        return
    }
​
    // 提取令牌
    tokenStr := authHeader[len("Bearer "):]
​
    // 解析和验证令牌
    secretKey := []byte("keytest")
    token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
        // 验证签名算法
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }
        return secretKey, nil
    })
    if err != nil {
        w.WriteHeader(http.StatusUnauthorized)
        fmt.Fprintf(w, "Invalid token: %v", err)
        return
    }
​
    // 检查令牌是否有效
    if _, ok := token.Claims.(jwt.MapClaims); !ok || !token.Valid {
        w.WriteHeader(http.StatusUnauthorized)
        fmt.Fprintf(w, "Invalid token")
        return
    }
​
    // 认证通过,继续处理请求
    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprintf(w, "Protected resource")
}