一文带你搞懂GitHub OAuth

有段时间没有写技术文章了,就是那种纯纯的技术文章,今天就给大家带来一篇比特仑苏还纯的技术文章,带你搞懂Github OAuth的使用方式。(注:全文使用的OAuth均指OAuth2.0)

什么是OAuth?它有什么用?

OAuth(Open Authorization)是一个开放标准的授权协议,用于授权第三方应用程序或服务访问用户在另一个服务提供者上存储的资源,而无需共享用户的用户名和密码。它的主要目的是使用户能够安全地授权第三方应用程序或服务来访问其受保护的资源,同时保护用户的敏感信息。

常见的,就比如我们使用QQ登录第三方网站时,就会弹出QQ提供的OAuth授权网站,当我们登录QQ成功时就会跳转到原网站并且授权成功,这就是一个典型的OAuth流程:

OAuth的主要作用是提供一种安全的、开放而又简易的标准,使得第三方无需知道用户的账号及密码,就可获取到用户的授权信息。它允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。

OAuth有以下优势:

  1. 安全性:OAuth提供了一个安全的认证方式,客户端无需使用用户的用户名和密码即可获得授权,从而避免了用户敏感信息的泄露。
  2. 开放性:OAuth是一个开放的认证标准,被许多服务提供商广泛采用,因此可以与许多服务进行集成。
  3. 简易性:OAuth协议设计简洁,易于实现和使用,使得开发者可以快速地开发出应用程序,而无需处理复杂的认证和授权机制。
  4. 灵活性:OAuth提供了多种授权方式,例如授权码模式、简化模式和纯令牌模式,以满足不同场景下的需求。
  5. 可扩展性:OAuth不断在发展中,OAuth 2.0等后续版本不断优化和改进,以适应不断变化的安全需求和技术环境。

总的来说,OAuth使得第三方应用程序或服务能够安全地获取用户的授权,并访问用户在其他服务提供者上存储的资源,同时保护了用户的隐私和安全。OAuth的优势在于其安全性、开放性、简易性、灵活性和可扩展性,使得它成为了一个广泛采用和推荐的认证和授权标准。

GitHub OAuth是怎样的一种OAuth?

GitHub OAuth是一种基于OAuth协议的身份验证和授权机制,是一种标准的OAuth2.0流程。

当第三方应用程序需要访问用户的GitHub资源时,它会引导用户前往GitHub的OAuth授权页面。用户在授权页面上确认授权后,第三方应用程序会收到一个访问令牌(access token),该令牌允许应用程序代表用户执行受限制的操作。令牌具有有效期,并且可以被用于向GitHub API发起请求。

GitHub OAuth的使用场景主要包括:

  1. 第三方登录:用户想要登录A网站,A网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要OAuth授权。例如,A网站允许GitHub登录,背后就是OAuth的流程。
  2. 应用集成:当需要将第三方应用程序集成到GitHub项目中时,例如CI/CD工具、代码质量分析工具等,可以通过OAuth来安全地访问GitHub上的数据。

GitHub OAuth的基本流程

GitHub OAuth流程如下:

  1. 在GitHub上注册一个OAuth App。注册完成后,会获得Client ID和Client Secret。
  2. 编写代码,登录时请求GitHub API,跳转到用户授权页面。
  3. 用户同意授权后,GitHub会携带code参数,请求callback URL。
  4. 服务器接收请求后获得code,再携带code、client_id、client_secret等数据POST请求API。
  5. 从GitHub的响应中获得token。
  6. 携带token请求API,获得用户信息。

通过OAuth,第三方应用程序可以在用户授权的情况下安全地访问GitHub上的数据,而不需要获取用户的GitHub凭据。

下面两张图是基于以上流程所画出:

第一张图是极简版的,前后端协调,将利用Token获取UserInfo的流程挪到了前端实现,这样的话确实可以使用最少的代码实现功能,但我个人感觉不够安全。

第二张图的流程相对丰富,本篇文章的代码也是使用的该流程,区别上面那张图的地方主要是Token获取UserInfo的流程也放在后端,并且通过cookie的方式将身份给到前端,这样我们可以更好的灵活控制cookie的作用域、过期时间等,相比第一张图的流程复杂但安全了一些。

不到200行代码实现一个GitHub OAuth!

首先是Server端代码,会有三个接口:

  • /login:客户端点击登录时访问,也是系统的默认登录接口,访问时会重定向返回一个跳转地址。
  • /token:授权回调地址,主要是GitHub OAuth服务方授权时请求的地址,用于返回code。
  • /userInfo:根据cookie获取用户信息地址,cookie取自于HTTP请求的header中。

服务端代码:

go 复制代码
var userMap = map[string]UserInfo{}

const (
	githubUri         = "https://github.com/login/oauth/authorize"
	githubAccessToken = "https://github.com/login/oauth/access_token"
	githubUserApi     = "https://api.github.com/user"
	redirectUri       = "http://localhost:8080/token" //地址必须注册到github的配置中
	clientID          = ""                            //TODO 填写自己的clientID
	clientSecret      = ""                            //TODO 填写自己的clientSecret
	sessionKey        = "test"
)

func main() {
	userMap = make(map[string]UserInfo)

	//前端静态文件地址
	fs := http.FileServer(http.Dir("public"))
	http.Handle("/", fs)

	//请求登录接口
	http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		uri := fmt.Sprintf("%s?client_id=%s&redirect_uri=%s", githubUri, clientID, url.QueryEscape(redirectUri))
		http.Redirect(w, r, uri, http.StatusFound)
	})

	//重定向时根据code获取token接口
	http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
		httpClient := http.Client{}
		if err := r.ParseForm(); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		code := r.FormValue("code")

		reqURL := fmt.Sprintf("%s?client_id=%s&client_secret=%s&code=%s", githubAccessToken, clientID, clientSecret, code)
		req, err := http.NewRequest(http.MethodPost, reqURL, nil)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		req.Header.Set("accept", "application/json")
		res, err := httpClient.Do(req)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		defer func() {
			_ = res.Body.Close()
		}()

		var t OAuthAccessResponse
		if err = json.NewDecoder(res.Body).Decode(&t); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		//set cookie
		cookie, err := genCookie(t.AccessToken)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		http.SetCookie(w, &http.Cookie{Name: sessionKey, Value: cookie, Path: "/", Domain: "localhost", Expires: time.Now().Add(time.Second * 3600)})

		w.Header().Set("Location", "/welcome.html")
		w.WriteHeader(http.StatusFound)
	})

	http.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie(sessionKey)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		bytes, err := json.Marshal(userMap[cookie.Value])
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		_, _ = w.Write(bytes)
	})
	_ = http.ListenAndServe(":8080", nil)
}

//根据token获取userInfo,置换出自定义的cookie
func genCookie(token string) (string, error) {
	httpClient := http.Client{}
	req, err := http.NewRequest(http.MethodGet, githubUserApi, nil)
	if err != nil {
		return "", err
	}

	req.Header.Set("Authorization", "token "+token)
	res, err := httpClient.Do(req)
	if err != nil {
		return "", err
	}
	bytes, err := io.ReadAll(res.Body)
	if err != nil {
		return "", err
	}

	cookie := uuid.NewString()
	var userInfo UserInfo
	if err = json.Unmarshal(bytes, &userInfo); err != nil {
		return "", err
	}

	userMap[cookie] = userInfo
	return cookie, nil
}

type OAuthAccessResponse struct {
	AccessToken string `json:"access_token"`
}

Client端代码:

客户端代码就比较简单了,就是携带token请求后端的userInfo接口获取用户信息。

html 复制代码
<body>
<a href="http://localhost:8080/login">点击登录</a>
</body>
<script>
    fetch('http://localhost:8080/userinfo', {}).then(res => res.json())
        .then(res => {
            const nameNode = document.createTextNode(`Welcome, ${res.name}`)
            document.body.appendChild(nameNode)
        })
</script>

注意点!!!

需要提前将完整的token接口的地址配置在GitHub OAuth App的配置中,否则会报错。

回顾总结

本文详细解读了GitHub OAuth的相关概念和操作流程。通过了解OAuth协议的工作原理和GitHub OAuth的应用场景,读者可以更好地理解如何通过OAuth授权机制安全地访问和使用GitHub上的资源。文章还提供了具体的操作步骤和注意事项,帮助读者在实际操作中避免常见问题。对于需要使用GitHub OAuth的开发者和用户来说,本文是一篇非常实用的参考资料。

参考:

docs.github.com/en/apps/oau...

blog.csdn.net/Mr_YanMingX...

相关推荐
A尘埃2 分钟前
SpringBoot的数据访问
java·spring boot·后端
yang-23073 分钟前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code8 分钟前
(Django)初步使用
后端·python·django
代码之光_198015 分钟前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
编程老船长28 分钟前
第26章 Java操作Mongodb实现数据持久化
数据库·后端·mongodb
IT果果日记1 小时前
DataX+Crontab实现多任务顺序定时同步
后端
姜学迁2 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健3 小时前
MQTT--Java整合EMQX
后端
北极小狐3 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端