目录
- 引入
- Oauth2概念
- Oauth2的角色
- Oauth2 的四种认证模式
- 授权码模式
- 简化模式 (隐式授权模式)
- 密码形式
- 客户端模式
- go web结合Github做auth认证([官方文档](https://docs.github.com/zh/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps))
- 参考文献
第三方登录是应用开发中的常用功能,通过第三方登录,可以更加容易的吸引用户来到我们的应用中。现在,很多网站都提供了第三方登录的功能,在他们的官网中,都提供了如何接入第三方登录的文档。
大多数网站提供的第三方登录都遵循OAuth协议,虽然大多数网站的细节处理都是不一致的,甚至会基于OAuth协议进行扩展,但大体上其流程是一定的。
引入
场景:快递员进入小区,被门禁系统拦截。
-
如果用户把自己的密码,告诉快递员,他就拥有了与用户同样的权限,危险较大,用户如果想取消他进入小区的权力,需要把自己的密码也得跟着改了,还得通知其他的快递员。
-
有没有一种办法,让快递员能够自由进入小区,又不必知道小区居民的密码。
授权机制的设计:
-
第一步,门禁系统的auth增加一个按钮,叫做"获取授权",快递员需要首先按这个按钮,去申请授权。
-
第二步,他按下按钮以后,屋主的手机就会跳出对话框:有人正在要求授权,系统还会显示该快递员的姓名、工号和所属的快递公司。
-
屋主确认请求属实,就点击按钮,告诉门禁系统,同意给予他进入小区的授权。
-
第三步,门禁系统得到的确认以后,向快递员显示一个进入小区的令牌(access token),只在短期内(比如七天)有效。
-
第四步,快递员向门禁系统输入令牌,进入小区。
为什么不是远程为快递员开门,而要为他单独生成一个令牌?这是因为快递员可能每天都会来送货,第二天他还可以复用这个令牌,另外,有的小区有多重门禁,快递员可以使用同一个令牌通过它们。
Oauth2概念
- OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方应用访问用户存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享用户数据的所有内容。
- OAuth在全世界得到广泛应用,目前的版本是2.0版。
- OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。
Oauth2的角色
OAuth2协议包含几种角色:Client(客户端)、Resource Owner(资源拥有者)、Authorization Server(认证服务器)、Resource Server(资源服务器)。
-
Resource owner 资源拥有者(用户):
能够授予对受保护资源的访问权限的实体。当资源所有者是一个人时,它被称为最终用户
-
Resource Server 资源服务器:
托管受保护资源的服务器,能够使用访问令牌接受和响应受保护资源请求。
-
Client 客户端:
代表资源所有者并经其授权发出受保护资源请求的应用程序。
-
Authorization server 授权服务器:
服务器在成功验证资源所有者并获得授权后向客户端颁发访问令牌。授权服务器和资源服务器之间的交互超出了本规范的范围。授权服务器可以是与资源服务器相同的服务器,也可以是单独的实体。 单个授权服务器可以发布多个资源服务器接受的访问令牌。
白话来讲:
- Client 客户端,即我们的应用就是一个Client。
- Resource Owner 资源所有者,即用户。
- Authorization Server 授权服务器,即提供第三方登录服务的服务器,如Github,Google。
- Resource Server 拥有资源信息的服务器,通常和授权服务器属于同一应用。
根据上图的信息,可以知道OAuth2的基本流程为:
- 客户端请求(引导)用户授权。
- 用户同意授权,并返回一个凭证(code)
- 客户端通过第二步的凭证(code)向授权服务器请求授权
- 授权服务器验证凭证(code)通过后,同意授权,并返回一个资源访问的凭证(Access Token)。
- 客户端通过第四步的凭证(Access Token)向资源服务器请求相关资源。
- 资源服务器验证凭证(Access Token)通过后,将客户端请求的资源返回。
Oauth2 的四种认证模式
- 授权码模式:常见的第三方平台登录功能基本都是使用这种模式。
- 简化模式:简化模式是不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌(token),一般如果网站是纯静态页面则可以采用这种方式。
- 密码形式:密码模式是用户把用户名密码直接告诉客户端,客户端使用说这些信息向授权服务器申请令牌(token)。
- 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。
授权码模式
这种方式是最常用的流程,安全性也最高。
简化模式 (隐式授权模式)
有些 Web 应用是纯前端应用,没有后端,这时就不能用上面的方式了,必须将令牌储存在前端。
RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。
密码形式
如果高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用去申请令牌,这种方式称为"密码式"(password)。
客户端模式
有的应用可能没有前端页面,就是一个后台。
在这个过程中好像没有用户什么事了,客户端模式给出的令牌,就是针对第三方应用的,而不是针对用户的。
go web结合Github做auth认证(官方文档)
-
先到官网上注册一个application:https://github.com/settings/developers
只有callback地址比较重要,其他的不影响认证流程: -
得到cliendt_id和client_Secret,cliendt_id是GitHub用来标识APP的,client_secret获取access_token时用到。
-
流程1:客户端请求(引导)用户授权,允许用户点击Login with Github使用Github进行登录
package mainimport ("fmt""io""log""net/http""net/url""strings" )var (clientID = ""clientSecrets = ""githubAuthApi = "https://github.com/login/oauth/authorize"githubTokenApi = "https://github.com/login/oauth/access_token" )func main() {http.HandleFunc("/", IndexHandler)http.HandleFunc("/login", LoginHandler)http.HandleFunc("/callback", CallbackHandler)if err := http.ListenAndServe("localhost:8080", nil); err != nil {log.Fatalln("could not start server:", err.Error())} }func IndexHandler(w http.ResponseWriter, r *http.Request) {templates := `<html><body><a href="/login">Login with Github</a></body></html>`if _, err := fmt.Fprintln(w, templates); err != nil {log.Fatalln("error:", err.Error())} }func LoginHandler(w http.ResponseWriter, r *http.Request) {// redirect到githubquery := url.Values{}query.Set("client_id", clientID)u, err := url.ParseRequestURI(githubAuthApi)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// 拼接get请求u.RawQuery = query.Encode()http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) }
-
流程2:用户进行github的权限校验,允许授权,github会跳转到既定的地址,并且将code传入
-
流程3:客户端通过第二步的凭证(code)向授权服务器请求授权,流程4:授权服务器验证凭证(code)通过后,同意授权,并返回一个资源访问的凭证(Access Token)。
func CallbackHandler(w http.ResponseWriter, r *http.Request) {// 接收github回调code := r.FormValue("code")formData := url.Values{}formData.Set("client_id", clientID)formData.Set("client_secret", clientSecrets)formData.Set("code", code)request, err := http.NewRequest(http.MethodPost, githubTokenApi, strings.NewReader(formData.Encode()))if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}request.Header.Set("Accept", "application/json")resp, err := http.DefaultClient.Do(request)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// 关闭bodydefer func(Body io.ReadCloser) {err = Body.Close()if err != nil {log.Println(err)}}(resp.Body)content, err := io.ReadAll(resp.Body)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}_, err = w.Write(content)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return} }
-
使用access_token获取用户信息:
// 添加头信息 Authorization: Bearer OAUTH-TOKEN // 用户接口 GET https://api.github.com/user
参考文献
- [1] 江南一点雨.做微服务绕不过的 OAuth2,松哥也来和大家扯一扯.
- [2] 我就是个菜鸟2021.oauth2基本概念
- [3] Java鱼仔.Oauth2是个什么东西?