目录

什么是 OAuth 2.0:不给密码也能授权的秘密

你肯定遇到过这种情况:打开一个新 App,它说"用微信登录",你点了之后跳转到微信,看到授权页面写着"该应用希望获取你的昵称和头像",你点击同意,然后就自动登录了。

整个过程你没有输入过密码,但这个 App 确实拿到了你的信息。

这就是 OAuth 2.0 在起作用。

它解决了一个听起来很矛盾的问题:怎么在不告诉对方密码的情况下,让对方访问你的东西?

为什么需要 OAuth 2.0

在 OAuth 2.0 出现之前,第三方应用想访问你的数据,只有一个办法:你把账号密码直接给它。

听起来就很不安全对吧?

确实,这种方式有几个大问题:

  • 密码泄露给第三方,万一它被黑了,你的账号也完了
  • 第三方拿到密码后能做任何事,包括改密码把你踢出去
  • 你没办法只授权部分功能,要么全给,要么不给
  • 想撤销授权?只能改密码,但这样会把所有第三方应用都踢下线

OAuth 2.0 就是为了解决这些问题而生的。

它引入了一个核心概念:用令牌代替密码

/img/what-is-oauth-2-0/comparison.png
OAuth2.0vs传统密码授权

OAuth 2.0 的核心思路

OAuth 2.0 的设计思路很简单:不直接给密码,而是给一个临时通行证(令牌)。

这个通行证有几个特点:

权限有限。比如只能读你的头像昵称,不能发朋友圈。

有效期短。通常几个小时到几天就过期,就算被盗影响也有限。

可以撤销。你随时可以在授权管理里取消某个 App 的访问,不影响其他应用。

无法修改密码。第三方拿着令牌只能干授权范围内的事,改不了你的密码。

整个授权过程涉及四个角色:

  • 资源所有者(你) — 数据的主人
  • 客户端(第三方 App) — 想要访问你数据的应用
  • 授权服务器 — 负责验证身份和发放令牌,比如微信的授权服务
  • 资源服务器 — 存储你数据的地方,比如微信的用户数据库

令牌是怎么拿到的

我们以最常见的"授权码模式"为例,看看一个完整的授权流程。

/img/what-is-oauth-2-0/token-flow.png
OAuth2.0令牌获取流程

假设你要用微信登录一个购物 App:

第 1 步:App 发起授权请求

购物 App 构造一个授权 URL,里面包含它的身份信息(client_id)、需要的权限范围(scope)、以及回调地址(redirect_uri)。然后把你重定向到这个 URL。

https://weixin.com/oauth/authorize?
  response_type=code&
  client_id=shopping_app_123&
  redirect_uri=https://shop.com/callback&
  scope=userinfo&
  state=random_string

第 2 步:你在微信授权页面登录并同意

你被跳转到微信的授权页面,输入微信账号密码登录(注意,密码是给微信的,不是给购物 App 的),然后看到授权范围,比如"获取昵称和头像"。你点击同意。

第 3 步:微信返回授权码

微信验证你的身份后,生成一个临时的授权码(authorization code),然后把你重定向回购物 App 的回调地址,授权码附在 URL 参数里:

https://shop.com/callback?code=AUTH_CODE_123&state=random_string

这个授权码只能用一次,而且有效期很短,通常只有 10 分钟。

第 4 步:App 用授权码换取令牌

购物 App 的后台服务器拿到授权码后,向微信的令牌接口发送请求,附上自己的 client_id 和 client_secret(密钥),以及刚拿到的授权码:

POST https://weixin.com/oauth/token
client_id=shopping_app_123
client_secret=app_secret_456
grant_type=authorization_code
code=AUTH_CODE_123
redirect_uri=https://shop.com/callback

第 5 步:微信返回访问令牌

微信验证授权码和应用身份后,返回一个访问令牌(access_token)和一个刷新令牌(refresh_token):

{
  "access_token": "ACCESS_TOKEN_ABC",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "REFRESH_TOKEN_XYZ",
  "scope": "userinfo"
}

第 6 步:App 用令牌请求你的数据

购物 App 拿着访问令牌,向微信的资源接口请求你的头像和昵称:

GET https://api.weixin.com/user/info
Authorization: Bearer ACCESS_TOKEN_ABC

微信验证令牌有效后,返回你的数据。整个过程结束。

用 Go 实现 OAuth 2.0 客户端

下面是一个完整的 Go 代码示例,展示如何实现授权码模式:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"golang.org/x/oauth2"
)

// 定义 OAuth 2.0 配置
var oauthConfig = &oauth2.Config{
	ClientID:     "your_client_id",
	ClientSecret: "your_client_secret",
	RedirectURL:  "http://localhost:8080/callback",
	Scopes:       []string{"read_user", "read_email"},
	Endpoint: oauth2.Endpoint{
		AuthURL:  "https://provider.com/oauth/authorize",
		TokenURL: "https://provider.com/oauth/token",
	},
}

func main() {
	// 路由:跳转到授权页面
	http.HandleFunc("/login", handleLogin)
	// 路由:处理授权回调
	http.HandleFunc("/callback", handleCallback)

	fmt.Println("服务运行在 http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

// 处理登录请求,重定向到授权页面
func handleLogin(w http.ResponseWriter, r *http.Request) {
	// 生成一个随机 state 用于防止 CSRF 攻击
	state := generateRandomState()
	
	// 构造授权 URL
	url := oauthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline)
	
	// 将 state 存储到 session(这里简化处理)
	http.SetCookie(w, &http.Cookie{
		Name:     "oauth_state",
		Value:    state,
		Expires:  time.Now().Add(10 * time.Minute),
		HttpOnly: true,
	})
	
	// 重定向到授权页面
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

// 处理授权回调
func handleCallback(w http.ResponseWriter, r *http.Request) {
	// 验证 state 参数,防止 CSRF
	stateCookie, err := r.Cookie("oauth_state")
	if err != nil || stateCookie.Value != r.URL.Query().Get("state") {
		http.Error(w, "state 参数不匹配", http.StatusBadRequest)
		return
	}

	// 获取授权码
	code := r.URL.Query().Get("code")
	if code == "" {
		http.Error(w, "授权码缺失", http.StatusBadRequest)
		return
	}

	// 用授权码交换访问令牌
	ctx := context.Background()
	token, err := oauthConfig.Exchange(ctx, code)
	if err != nil {
		log.Printf("令牌交换失败: %v", err)
		http.Error(w, "获取令牌失败", http.StatusInternalServerError)
		return
	}

	// 使用令牌访问受保护的资源
	userInfo, err := getUserInfo(ctx, token)
	if err != nil {
		log.Printf("获取用户信息失败: %v", err)
		http.Error(w, "获取用户信息失败", http.StatusInternalServerError)
		return
	}

	// 返回用户信息
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(userInfo)
}

// 使用访问令牌获取用户信息
func getUserInfo(ctx context.Context, token *oauth2.Token) (map[string]interface{}, error) {
	client := oauthConfig.Client(ctx, token)
	
	resp, err := client.Get("https://provider.com/api/user")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var userInfo map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
		return nil, err
	}

	return userInfo, nil
}

// 生成随机 state 字符串
func generateRandomState() string {
	// 实际项目中应使用加密随机数生成器
	return fmt.Sprintf("state_%d", time.Now().Unix())
}

这段代码展示了完整的授权流程:生成授权 URL、处理回调、交换令牌、访问资源。实际项目中还需要处理令牌刷新、错误重试、安全存储等细节。

OAuth 2.0 的安全要点

虽然 OAuth 2.0 比直接给密码安全多了,但如果用不好还是会出问题。几个关键的安全实践:

必须使用 HTTPS

授权码、令牌在网络传输,如果用 HTTP 明文传输,中间人可以轻松截获。

生产环境必须全程 HTTPS。

验证 state 参数

授权请求和回调之间传递一个随机 state 参数,回调时必须验证是否一致。这能防止 CSRF 攻击,避免攻击者伪造回调。

令牌不要暴露在前端

访问令牌应该存在后端 session 里,不要放在浏览器的 localStorage 或 Cookie 中。前端只持有 session ID,令牌只在后端使用。

限制令牌权限范围

申请授权时,只要必需的最小权限(scope)。能用"只读用户信息"就不要申请"读写所有数据"。

设置合理的过期时间

访问令牌有效期不要太长,建议 1-2 小时。长期访问通过刷新令牌(refresh token)来实现,刷新令牌有效期可以设置几天到几个月。

做好令牌存储

令牌是敏感信息,存储时要加密。如果是移动端,存在系统提供的安全存储(iOS Keychain、Android Keystore)里。

常见问题

OAuth 2.0 和 OAuth 1.0 有什么区别?

OAuth 1.0 要求对每个请求做签名,实现复杂且容易出错。

OAuth 2.0 简化了流程,不再要求签名,而是依赖 HTTPS 保证传输安全。

可以说,OAuth 2.0 用 HTTPS 的安全性换取了实现的简单性。

访问令牌和刷新令牌有什么区别?

访问令牌(access token)用来访问资源,有效期短。刷新令牌(refresh token)用来获取新的访问令牌,有效期长。这样即使访问令牌泄露,影响也是有限的,而且不需要用户重新授权就能无缝续期。

OAuth 2.0 能做身份认证吗?

OAuth 2.0 是授权协议,不是认证协议。

它只告诉你"这个令牌有权限访问某些资源",但不保证"持有令牌的人是谁"。

如果要做身份认证(登录),应该用 OpenID Connect,它是基于 OAuth 2.0 的身份认证层。

授权码为什么要分两步交换?

授权码在浏览器前端传输(可能被劫持),但令牌在后端服务器之间交换(相对安全)。

而且授权码只能用一次,即使被截获也无法重放。这个两步设计大大降低了令牌泄露的风险。

令牌被盗了怎么办?

如果访问令牌泄露,第一时间调用令牌撤销接口(revoke endpoint)让它失效。

这也是为什么访问令牌要设置短过期时间——即使没来得及撤销,攻击者能利用的时间窗口也很短。

总结一下

OAuth 2.0 通过引入令牌机制,在不暴露密码的前提下,让用户可以安全地授权第三方应用访问自己的资源。

它的核心优势是:权限可控、风险隔离、可撤销授权。授权码模式通过两步交换,在安全性和易用性之间取得了很好的平衡,是目前最推荐的方式。

开发时记住几个要点:全程 HTTPS、验证 state、令牌后端存储、最小权限原则、设置合理过期时间。把这些做到位,OAuth 2.0 就能成为你应用安全的有力保障。

如果你在使用 OAuth 2.0 时遇到什么问题,或者对某个细节有疑问,欢迎评论区一起讨论!

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/what-is-oauth-2-0/

备用原文链接: https://blog.fiveyoboy.com/articles/what-is-oauth-2-0/