前端鉴权之OAuth2.0

前端鉴权之OAuth2.0

一、OAuth是什么

  1. 作用:
    • 客户端(第三方应用程序)安全可控地获取用户授权,与服务提供商进行互动
    • 相当于让云冲印(客户端)这个网站,获取用户授权,读取储存在Google(服务提供商)上的图片
  2. 角色
  • Third-party application:第三方应用程序,又称"客户端",即"云冲印"。
  • HTTP service:HTTP服务提供商,简称"服务提供商",即Google。
  • Resource Owner:资源所有者,又称"用户"。
  • User Agent:用户代理,指浏览器。
  • Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
  • Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
  1. 思路
    • OAuth在"客户端"与"服务提供商"之间,设置了一个授权层
    • 用户在登录的时候,指定授权层令牌的权限范围和有效期
    • "客户端"使用令牌登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料

在这里插入图片描述

二、OAuth如何实现

  1. 客户端的四种授权方式
    • 授权码模式
      • 特点:通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动

        在这里插入图片描述

    • 简化模式
      • 特点:不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌

        在这里插入图片描述

    • 密码模式
      • 特点:客户端使用用户向客户端提供的用户名和密码,向"服务商提供商"索要授权
        在这里插入图片描述
    • 客户端模式
      • 特点:用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,不存在授权问题

        在这里插入图片描述

  2. 访问令牌的更新
    • 客户端发出更新令牌的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
        
  3. 访问令牌的更新
    • 客户端发出更新令牌的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
        
  4. 授权码模式实现例子
    // 在回调页面处理授权码
    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;
    });
    

三、应用场景

  1. 路由守卫

    // 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();
      }
    });
    
  2. 用户信息获取

    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;
      }
    }
    
  3. 注销处理

    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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值