为什么需要统一的认证授权
公司里的系统越来越多,每个系统各管一套账号密码,用户要记一堆口令,运维要维护一堆账号表,安全上还容易出现弱口令与一人多账号的混乱。认证授权的统一,是把"你是谁"(认证)和"你能干什么"(授权)集中到一处:一个身份体系打通所有业务系统,一次登录到处可用,权限由平台统一管控。
OAuth 2.0 解决的是授权问题:应用能不能替用户访问某类资源、能访问多久,通过颁发令牌来约束。单点登录(SSO)解决的是体验问题:在统一认证中心登录一次,其他系统自动获得会话。两者经常组合使用,授权码模式加 SSO 会话,是当前企业级应用的主流形态。
- 认证确认身份,回答你是谁。
- 授权授予权限,回答你能做什么。
- 授权码换取令牌的中间凭证。
- SSO一次登录,多系统共享会话。
OAuth 2.0 的四种授权模式
OAuth 2.0 按客户端类型和信任程度,定义了四种授权模式。授权码模式最安全,适用于有后端的 Web 应用,浏览器跳转授权页、拿授权码、后端换令牌,密钥不暴露在前端;隐式模式把令牌直接给前端,现在已不推荐,被授权码加 PKCE 取代;密码模式由应用代收用户名密码换令牌,只适用于第一方应用;客户端凭证模式用密钥直接换令牌,适用于服务间调用。
// 客户端凭证模式:服务间调用
POST /token
grant_type=client_credentials
client_id=service-a
client_secret=****
选型口诀:有后端用授权码,纯前端加 PKCE,第一方应用才考虑密码模式,机器对机器用客户端凭证。理解这四种模式,就能看懂绝大多数授权协议文档。
授权码流程实战
授权码流程是前后端协作的典型。第一步,前端把用户重定向到授权中心,带上 client_id、redirect_uri、response_type=code 和随机 state;第二步,用户登录并同意授权后,授权中心带着 code 和 state 重定向回来;第三步,前端把 code 交给后端,后端用 client_id 与 client_secret 换 token;最后,应用拿 token 访问受保护资源。
// 后端用授权码换令牌
const resp = await fetch("https://auth.example.com/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "authorization_code",
code: authCode,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret,
}),
});
const { access_token } = await resp.json();
state 参数用来防 CSRF:发起授权时生成随机值存好,回调时校验一致才继续,否则攻击者可以诱导用户把授权结果绑定到恶意会话上。redirect_uri 要精确校验白名单,不能只比对前缀。
令牌:Access Token 与 Refresh Token
Access Token 是访问资源的凭证,有效期短,通常十几分钟到几小时,过期后不能续期;Refresh Token 是换新令牌的凭证,有效期长,保存在后端,过期后用户需要重新登录。双令牌机制让访问令牌保持短命、降低泄露风险,同时长会话又不打扰用户。
- 短命访问令牌过期快,泄露窗口小。
- 刷新刷新令牌换新,无需重新认证。
- 撤销登出或异常时主动失效令牌。
// 令牌过期后刷新
async function refresh() {
const resp = await fetch("/token", {
method: "POST",
body: JSON.stringify({ refresh_token: storedRefreshToken }),
});
const data = await resp.json();
accessToken = data.access_token;
refreshToken = data.refresh_token || refreshToken;
}
刷新令牌也有讲究:刷新后轮换 refresh token,旧令牌作废;发现旧令牌仍被使用时,视为异常,提示用户重新登录。客户端要处理令牌并发刷新,避免多请求同时刷新导致互相覆盖。
单点登录:Session 与 JWT 的取舍
单点登录的会话载体有两条路线。Session 路线:统一认证中心维护服务端会话,各业务系统通过校验会话凭证间接确认登录,实现简单、可随时吊销,但依赖共享会话存储;JWT 路线:把用户信息签名进令牌,业务系统验签即可信任,无状态、适合跨域与微服务,但吊销麻烦、令牌膨胀。
// JWT 载荷示意
{
"sub": "user-1001",
"iss": "https://auth.example.com",
"exp": 1710000000,
"scope": ["profile", "orders"]
}
实际项目常做混合:核心业务用 Session 会话保安全可控,开放平台对外用 JWT 降低接入成本。无论哪种,认证中心都是唯一信任源,业务系统不要自己发明会话协议,否则 SSO 形同虚设。
安全加固与常见误区
认证授权是安全重地,几个高频坑要避开:令牌放在 URL 里,会被日志和浏览器历史记录泄露;access token 存进 localStorage,XSS 一打全丢,应放内存或 HttpOnly Cookie;redirect_uri 校验不严,授权码被截获;state 不校验,CSRF 可劫持回调;刷新令牌明文入库,数据库泄露即全线沦陷,应加密存储。
- 传输全程 HTTPS,令牌不走 URL。
- 存储令牌加密落库,前端少存令牌。
- 校验state 防 CSRF,URI 白名单。
- 监控异常刷新与频繁换码要告警。
另一个误区是以为拿到令牌就是永久授权:scope 要在令牌里显式声明,资源方按 scope 鉴权;用户改密、离职、风控触发时,要能立即吊销令牌与会话。授权记录要留审计日志,谁在什么时间授权给了哪个应用,事后可查。
落地清单
- 统一走授权码模式,纯前端加 PKCE,禁止隐式模式。
- access token 短命,refresh token 轮换并加密存储。
- state 参数必校验,redirect_uri 精确白名单。
- 令牌不放 URL,不存 localStorage,优先 HttpOnly Cookie。
- SSO 会话与 JWT 按场景取舍,认证中心唯一信任源。
- 登出、改密、风控时能即时吊销,授权全程留审计日志。