Skip to content

go 第三方github登录

Published: at 03:41 PM | 3 min read

给本网站加了一个第三方github登录的功能

在github上新建oauth app

点击github登录

html标签

<a href="/githublogin">github</a>

路由

router.GET("/githublogin", controller.GithubLogin)

处理登录,向github发起login请求

func GithubLogin(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "https://github.com/login/oauth/authorize?client_id=531ad8e4517595748d97&state=123456789")
}

此时会打开github授权登录的页面,输入用户名和密码授权登录

github登录成功后回调

上文中设置的Authorization callback URL就是回调到本网站的地址,如我的是:

http://ningto.com/github_oauth_callback_comment

给它增加路由

router.GET("/github_oauth_callback_comment", controller.GithubOAuthCallbackComment)

处理github回调后的路由

func GithubOAuthCallbackComment(c *gin.Context) {
	errmsg := ""
	for {
		// 第一步,获取github回调的code和state
		code := c.Query("code")
		state := c.Query("state")
	
		// 第二步,POST获取access token
		payload := make(map[string]interface{})
		payload["client_id"] = "531ad8e4517595xxxxxx"
		payload["client_secret"] = "bf123fc9fe25a30e3e33d7a07daf825b73xxxxxx"
		payload["code"] = code
		payload["state"] = state
		data, err := json.Marshal(payload)
		if err != nil {
			errmsg = "json marsha1 error"
			break
		}
		reader := bytes.NewReader(data)
		url := "https://github.com/login/oauth/access_token"
		request, err := http.NewRequest("POST", url, reader)
		if err != nil {
			errmsg = "http new post request error"
			break
		}
		request.Header.Set("Accept", "application/json;charset=UTF-8")
		request.Header.Set("Content-Type", "application/json;charset=UTF-8")
		client := http.Client{}
		rsp, err := client.Do(request)
		if err != nil {
			errmsg = "client do request error"
			break
		}
		var jsonAccessToken AccessTokenResponse
		err = json.NewDecoder(rsp.Body).Decode(&jsonAccessToken)
		if err != nil {
			errmsg = "json decoder access token response error"
			break
		}
		token := jsonAccessToken.AccessToken
	
		// 第三步,使用access token获取用户基本信息
		url = "https://api.github.com/user?access_token=" + token
		rsp, err = http.Get(url)
		if err != nil {
			errmsg = "http get access token error"
			break
		}
	
		var jsonLogin GithubLoginResponse
		err = json.NewDecoder(rsp.Body).Decode(&jsonLogin)
		if err != nil {
			errmsg = "json decode error"
			break
		}
	
		// 第四步,判断数据库中是否已存在此用户
		user, err := model.GetUser("github", jsonLogin.Login)
		if err != nil {
			newUser := model.User {
				Id: bson.NewObjectId(),
				Provider: "github",
				Login: jsonLogin.Login,
				Password: "",
				Token: token,
			}
			model.InsertUser(newUser)
		} else {
			model.UpdateUserToken(user, token)
		}
	
		// 第五步,将token保存在session中,浏览器端会将token加密后放在cookie中,
		// 当前用户再次进行http请求时判断浏览器传过来的token是否在session中,
		// 如果存在表示已登录
		session := sessions.Default(c)
		session.Set("token", token)
		if session.Save() != nil {
			errmsg = "session save token failed"
		}
		break
	}

	// 完成
	if len(errmsg) > 0 {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"errmsg": errmsg,
		})
	} else {
		c.Redirect(http.StatusFound, "/")
	}
}

下面定义的两个结构体是github返回的json应答,由于我只需要用到部分数据,所以没有全部定义

type (
	AccessTokenResponse struct {
		AccessToken string `json:"access_token"`
		TokenType string `json:"token_type"`
		Scope string `json:"scope"`
	}

	GithubLoginResponse struct {
		Login string `json:"login"`
		Id int `json:"id"`
		AvatarUrl string `json:"avatar_url"`
	}
)