在逐步学习完青训营的课程后,我了解到了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、常见的用户认证方式
- 用户名和密码认证:这是最常见的认证方式,用户需要提供其用户名和密码来验证身份。通常使用表单提交或基本身份验证(HTTP Basic Authentication)进行传输。
- OAuth 认证:OAuth 是一种开放标准的认证协议,通过授权服务器和资源服务器的交互,允许用户使用第三方应用程序(如社交媒体平台)进行认证,而无需共享其用户名和密码。
- OpenID Connect 认证:OpenID Connect 是建立在 OAuth 2.0 基础上的认证协议,它允许用户使用现有的身份提供者(如 Google、Microsoft)进行认证,并从认证服务器获取身份令牌。
- SAML 认证:Security Assertion Markup Language(SAML)是一种基于 XML 的认证标准,允许不同域之间的单点登录(Single Sign-On)和授权。
- JWT 认证:JSON Web Token(JWT)是一种轻量级的安全令牌,可以在用户和服务之间安全地传输信息。通常用于无状态的身份验证和授权。
- 双因素认证:双因素认证要求用户提供两个或多个验证因素,例如密码和手机验证码、指纹识别等。这增加了系统的安全性,防止未经授权的访问。
目前在web方面常用的是 jwt 认证,在go语言中,官方也提供了 jwt-go 包来处理JWT令牌。
JWT 通常由三个部分组成:
- 头部(Header):JWT 的头部通常由两部分组成,即令牌类型(typ)和签名算法(alg)信息。这些信息以 JSON 对象的形式进行编码,并使用 Base64 编码进行传输。
- 载荷(Payload):载荷包含有关 JWT 的声明和其他相关数据。它可以包含标准声明(如发行人、到期时间、主题等)和自定义声明。载荷也被编码为 JSON 对象,并使用 Base64 编码进行传输。
- 签名(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")
}