前端鉴权之OAuth2.0
一、OAuth是什么
- Third-party application:第三方应用程序,又称"客户端",即"云冲印"。
- HTTP service:HTTP服务提供商,简称"服务提供商",即Google。
- Resource Owner:资源所有者,又称"用户"。
- User Agent:用户代理,指浏览器。
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
-
思路
- OAuth在"客户端"与"服务提供商"之间,设置了一个授权层
- 用户在登录的时候,指定授权层令牌的权限范围和有效期
- "客户端"使用令牌登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料
二、OAuth如何实现
-
客户端的四种授权方式
-
访问令牌的更新
-
客户端发出更新令牌的HTTP请求,包含以下参数:
-
granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。
-
refresh_token:表示早前收到的更新令牌,必选项。
-
scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
-
-
-
访问令牌的更新
-
客户端发出更新令牌的HTTP请求,包含以下参数:
-
granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。
-
refresh_token:表示早前收到的更新令牌,必选项。
-
scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
-
-
-
授权码模式实现例子
// 在回调页面处理授权码 async function handleCallback() { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const error = urlParams.get('error'); if (error) { console.error('OAuth Error:', error); return; } try { const codeVerifier = sessionStorage.getItem('pkce_code_verifier'); const tokenResponse = await axios.post( process.env.VUE_APP_OAUTH_TOKEN_URL, new URLSearchParams({ grant_type: 'authorization_code', code, client_id: process.env.VUE_APP_OAUTH_CLIENT_ID, redirect_uri: process.env.VUE_APP_OAUTH_REDIRECT_URI, code_verifier: codeVerifier }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } ); // 安全存储令牌(建议使用内存存储) sessionStorage.setItem('oauth_data', JSON.stringify({ access_token: tokenResponse.data.access_token, refresh_token: tokenResponse.data.refresh_token, expires_in: Date.now() + (tokenResponse.data.expires_in * 1000) })); // 清理临时数据 sessionStorage.removeItem('pkce_code_verifier'); window.history.replaceState({}, document.title, window.location.pathname); } catch (error) { console.error('Token exchange failed:', error); } }
// Axios 请求拦截器 axios.interceptors.request.use(config => { const oauthData = JSON.parse(sessionStorage.getItem('oauth_data')); if (oauthData?.access_token) { config.headers.Authorization = `Bearer ${oauthData.access_token}`; } return config; }); // 令牌自动刷新 async function refreshTokenIfNeeded() { const oauthData = JSON.parse(sessionStorage.getItem('oauth_data')); if (!oauthData || Date.now() < oauthData.expires_in) return; try { const response = await axios.post( process.env.VUE_APP_OAUTH_TOKEN_URL, new URLSearchParams({ grant_type: 'refresh_token', refresh_token: oauthData.refresh_token, client_id: process.env.VUE_APP_OAUTH_CLIENT_ID }) ); sessionStorage.setItem('oauth_data', JSON.stringify({ ...response.data, expires_in: Date.now() + (response.data.expires_in * 1000) })); } catch (error) { // 刷新失败时跳转到登录 sessionStorage.removeItem('oauth_data'); window.location.href = '/login'; } } // 在每次 API 调用前检查 axios.interceptors.request.use(async config => { await refreshTokenIfNeeded(); return config; });
三、应用场景
-
路由守卫
// Vue Router 示例 router.beforeEach(async (to, from, next) => { const requiresAuth = to.matched.some(record => record.meta.requiresAuth); const isAuthenticated = checkAuthStatus(); // 检查令牌有效性 if (requiresAuth && !isAuthenticated) { next('/login'); } else { next(); } });
-
用户信息获取
async function fetchUserProfile() { try { const response = await axios.get('https://api.server.com/userinfo'); return response.data; } catch (error) { if (error.response?.status === 401) { sessionStorage.removeItem('oauth_data'); window.location.reload(); } throw error; } }
-
注销处理
async function logout() { // 调用 OAuth 服务端的注销端点 window.location.href = `https://auth-server.com/logout? client_id=${process.env.VUE_APP_OAUTH_CLIENT_ID}& post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`; // 本地清理 sessionStorage.removeItem('oauth_data'); }
参考资料
https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
https://www.cnblogs.com/MDGE/p/18386458