随着移动互联网的发展和普及,越来越多的网站和应用程序都开始使用RESTful API来服务于他们的用户。但是,在开发这些API时,我们需要考虑到用户的身份验证和授权问题。OAuth2是一种流行的身份验证和授权协议,可以为我们解决这些问题。
在本文中,我们将介绍如何使用Golang编写一个带有OAuth2身份验证的RESTful API,其中包括以下五个部分:
1. 什么是OAuth2
2. 初始化Golang项目
3. 基于Golang的RESTful API
4. 实现OAuth2身份验证
5. 用Postman测试API
## 1. 什么是OAuth2?
OAuth2是一个用于授权的开放标准,允许用户授权第三方应用程序访问其在另一个服务上的数据,而无需将用户名和密码提供给第三方应用程序。
OAuth2规范定义了四个角色:
- 资源所有者:拥有被保护的资源,可以授权客户端访问该资源。
- 客户端:第三方应用程序,需要访问资源所有者的资源。
- 授权服务器:负责验证资源所有者并返回令牌。
- 资源服务器:存储和提供被保护的资源。
为了理解OAuth2,让我们假设我们正在开发一个应用程序,该应用程序需要访问我们的用户存储服务,并获取用户信息。为了实现这一目标,我们可以使用OAuth2:
- 用户在我们的应用程序中点击“连接到用户存储服务”按钮。
- 我们的应用程序将用户重定向到用户存储服务的授权服务器。
- 用户在授权服务器上输入其凭据。
- 授权服务器验证凭据,并返回一个访问令牌。
- 我们的应用程序将此令牌用于访问用户存储服务的资源服务器。
## 2. 初始化Golang项目
首先,我们需要创建一个新的golang项目。你可以使用以下命令进行初始化:
```go
mkdir restful-api-oauth2
cd restful-api-oauth2
go mod init restful-api-oauth2
```
然后,我们需要安装一些依赖项。我们可以使用以下命令安装gorilla/mux和gorm:
```go
go get -u github.com/gorilla/mux
go get -u github.com/jinzhu/gorm
```
## 3. 基于Golang的RESTful API
接下来,我们将创建一个RESTful API,该API将允许我们创建,更新,检索和删除用户。我们将使用Gorilla mux来处理HTTP路由,GORM作为我们的ORM。
首先,我们将创建一个"user"文件夹,其中将存储所有与用户相关的代码。首先,我们将创建一个"user.go"文件,该文件将包含我们的用户结构体和我们的数据库模型定义。
用户结构体:
```go
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
PasswordHash string `json:"password_hash"`
}
```
数据库模型:
```go
func Migrate(db *gorm.DB) {
db.AutoMigrate(&User{})
}
```
接下来,我们需要在"user"文件夹中创建一个"user_controller.go"文件,该文件将包含所有处理HTTP路由的代码。在这个文件中,我们将处理以下HTTP请求:
- GET /users:获取所有用户。
- GET /users/{id}:按id获取用户。
- POST /users:创建用户。
- PUT /users/{id}:按id更新用户。
- DELETE /users/{id}:按id删除用户。
我们将使用以下代码定义这些HTTP请求的处理程序:
```go
func GetAllUsers(w http.ResponseWriter, r *http.Request) {
// 获取所有用户
}
func GetUserById(w http.ResponseWriter, r *http.Request) {
// 按id获取用户
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
// 创建用户
}
func UpdateUser(w http.ResponseWriter, r *http.Request) {
// 按id更新用户
}
func DeleteUser(w http.ResponseWriter, r *http.Request) {
// 按id删除用户
}
```
接下来,我们将使用Gorilla mux来定义这些HTTP路由。我们将在"main.go"中完成这项工作:
```go
func main() {
router := mux.NewRouter()
db, _ := gorm.Open("sqlite3", "./restful-api-oauth2.db")
defer db.Close()
user.Migrate(db)
router.HandleFunc("/users", user.GetAllUsers).Methods("GET")
router.HandleFunc("/users/{id}", user.GetUserById).Methods("GET")
router.HandleFunc("/users", user.CreateUser).Methods("POST")
router.HandleFunc("/users/{id}", user.UpdateUser).Methods("PUT")
router.HandleFunc("/users/{id}", user.DeleteUser).Methods("DELETE")
log.Fatal(http.ListenAndServe(":8080", router))
}
```
现在,我们已经完成了一个最基本的RESTful API,可以使用以下命令运行它:
```go
go run main.go
```
## 4. 实现OAuth2身份验证
现在,我们已经有了一个基本的RESTful API,但是它没有身份验证。我们将使用OAuth2来添加身份验证。
首先,我们需要安装以下第三方库:golang.org/x/oauth2,golang.org/x/oauth2/google和github.com/dgrijalva/jwt-go:
```go
go get -u golang.org/x/oauth2
go get -u golang.org/x/oauth2/google
go get -u github.com/dgrijalva/jwt-go
```
接下来,我们将创建一个"auth"文件夹,其中将存储所有与OAuth2身份验证相关的代码。
我们将使用Google OAuth2作为我们的身份验证提供者。我们将使用Google提供的OAuth2客户端ID和客户端密码来获取访问令牌。我们将使用访问令牌来验证用户。
首先,我们需要使用以下代码创建一个OAuth2配置:
```go
var (
googleOauthConfig *oauth2.Config
)
func init() {
googleOauthConfig = &oauth2.Config{
RedirectURL: "http://localhost:8080/auth/google/callback",
ClientID: "",
ClientSecret: "",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
},
Endpoint: google.Endpoint,
}
}
```
我们需要将"ClientID"和"ClientSecret"替换为我们的Google OAuth2客户端ID和客户端密码。
然后,我们将使用以下代码定义我们的"auth"文件夹中的路由:
```go
func InitAuthRoutes(r *mux.Router) {
authRouter := r.PathPrefix("/auth").Subrouter()
authRouter.HandleFunc("/google", handleGoogleLogin)
authRouter.HandleFunc("/google/callback", handleGoogleCallback)
}
```
我们将使用以下代码定义我们的"/auth/google"路由的处理程序:
```go
func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL("state")
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
token, err := googleOauthConfig.Exchange(r.Context(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusBadRequest)
return
}
client := googleOauthConfig.Client(r.Context(), token)
userInfo, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
defer userInfo.Body.Close()
data, _ := ioutil.ReadAll(userInfo.Body)
// 在此处解析JWT并验证用户
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
```
在"handleGoogleCallback"函数中,我们将使用Google OAuth2配置来获取一个访问令牌。我们将使用此令牌来获取用户信息。然后,我们将解析JWT并验证用户。在此之后,我们将重定向用户到首页。
在"handleGoogleCallback"函数中,我们还需要解析JWT并验证用户。我们将使用以下代码完成此操作:
```go
func validateIDToken(idTokenString string) (*jwt.Token, error) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signingMethod: %v", token.Header["alg"])
}
resp, err := http.Get("https://www.googleapis.com/oauth2/v3/certs")
defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body)
keySet := jwk.NewSet()
err = json.Unmarshal(data, &keySet)
if err != nil {
return nil, err
}
kid, ok := token.Header["kid"].(string)
if !ok {
return nil, fmt.Errorf("missing kid in token header")
}
keys := keySet.LookupKeyID(kid)
if len(keys) == 0 {
return nil, fmt.Errorf("unable to find key for kid: %v", kid)
}
key, err := keys[0].Materialize()
if err != nil {
return nil, err
}
return key, nil
}
return jwt.Parse(idTokenString, keyFunc)
}
```
此函数将使用Google提供的公共密钥集来验证JWT。如果JWT有效,则函数将返回解析后的JWT令牌。
现在,我们已经完成了OAuth2身份验证的实现。我们可以使用以下命令运行我们的应用程序:
```go
go run main.go
```
## 5. 用Postman测试API
现在,我们已经有了一个带有OAuth2身份验证的RESTful API。现在,我们将使用Postman测试API。
首先,我们需要使用Postman获取访问令牌。我们将使用Google OAuth2作为我们的身份验证提供者。我们将使用以下URL获取我们的访问令牌:
```
https://accounts.google.com/o/oauth2/auth?client_id=&redirect_uri=http://localhost:8080/auth/google/callback&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email&access_type=offline
```
我们需要将"client_id"替换为我们的Google OAuth2客户端ID。
接下来,我们将使用Postman测试所有HTTP请求。在测试这些请求之前,我们需要将我们的访问令牌放入Postman的环境变量中。我们可以使用以下代码来获取访问令牌:
```go
func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
token, err := googleOauthConfig.Exchange(r.Context(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusBadRequest)
return
}
client := googleOauthConfig.Client(r.Context(), token)
userInfo, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
defer userInfo.Body.Close()
data, _ := ioutil.ReadAll(userInfo.Body)
// 在此处解析JWT并验证用户
jwtToken, _ := validateIDToken(token.Extra("id_token").(string))
// 将令牌设置为Postman环境变量
os.Setenv("ACCESS_TOKEN", jwtToken.Raw)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
```
现在,我们已经将我们的访问令牌设置为Postman的环境变量中。我们可以使用以下代码来从Postman执行HTTP请求:
```go
func GetAllUsers(w http.ResponseWriter, r *http.Request) {
accessToken := os.Getenv("ACCESS_TOKEN")
// 使用访问令牌从Postman发出HTTP请求
}
func GetUserById(w http.ResponseWriter, r *http.Request) {
accessToken := os.Getenv("ACCESS_TOKEN")
// 使用访问令牌从Postman发出HTTP请求
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
accessToken := os.Getenv("ACCESS_TOKEN")
// 使用访问令牌从Postman发出HTTP请求
}
func UpdateUser(w http.ResponseWriter, r *http.Request) {
accessToken := os.Getenv("ACCESS_TOKEN")
// 使用访问令牌从Postman发出HTTP请求
}
func DeleteUser(w http.ResponseWriter, r *http.Request) {
accessToken := os.Getenv("ACCESS_TOKEN")
// 使用访问令牌从Postman发出HTTP请求
}
```
现在,我们已经使用Postman测试了我们的RESTful API,并且成功地实现了OAuth2身份验证。