Files
lingniu-platform/sdk/frontend/oauth2-login-sdk/README.md
2026-02-09 11:24:51 +08:00

12 KiB
Raw Blame History

安装

npm install unified-login-sdk --save
# 或
yarn add unified-login-sdk

快速开始

基本使用

import unifiedLoginSDK from 'unified-login-sdk';

// 初始化配置
unifiedLoginSDK.init({
  clientId: 'your-client-id',
  authorizationEndpoint: 'https://auth.example.com/authorize',
  tokenEndpoint: 'https://auth.example.com/token',
  userInfoEndpoint: 'https://auth.example.com/userinfo',
  redirectUri: 'https://your-app.example.com/callback',
  storageType: 'localStorage',
  autoRefreshToken: true,
  tenantId: 'your-tenant-id' // 可选会自动添加到请求头中的tenant-id字段
});

// 登录
document.getElementById('login-btn')?.addEventListener('click', () => {
  unifiedLoginSDK.login();
});

// 处理回调
if (unifiedLoginSDK.isAuthenticated()) {
  // 已登录,获取用户信息
  unifiedLoginSDK.getUserInfo().then(userInfo => {
    console.log('User info:', userInfo);
  });
} else if (unifiedLoginSDK.isCallback()) {
  // 处理授权回调
  unifiedLoginSDK.handleCallback().then(userInfo => {
    console.log('Login successful:', userInfo);
    // 跳转到首页
    window.location.href = '/';
  }).catch(error => {
    console.error('Login failed:', error);
  });
}

// 退出登录
document.getElementById('logout-btn')?.addEventListener('click', () => {
  unifiedLoginSDK.logout().then(() => {
    console.log('Logout successful');
    window.location.href = '/login';
  });
});

核心功能

初始化配置

unifiedLoginSDK.init({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret', // 可选,某些场景下需要
  authorizationEndpoint: 'https://auth.example.com/authorize',
  tokenEndpoint: 'https://auth.example.com/token',
  userInfoEndpoint: 'https://auth.example.com/userinfo',
  redirectUri: 'https://your-app.example.com/callback',
  storageType: 'localStorage', // 可选默认localStorage
  autoRefreshToken: true, // 可选默认true
  permissionsEndpoint: 'https://auth.example.com/permissions' // 可选,权限端点
});

登录流程

  1. 调用login()方法跳转到授权页面
  2. 用户在授权页面登录并授权
  3. 授权服务器重定向到配置的redirectUri
  4. 调用handleCallback()方法处理授权回调,获取用户信息

Token管理

// 获取访问令牌
const accessToken = unifiedLoginSDK.getAccessToken();

// 刷新令牌
unifiedLoginSDK.refreshToken().then(() => {
  console.log('Token refreshed');
}).catch(error => {
  console.error('Failed to refresh token:', error);
});

// 检查是否已认证
const isAuthenticated = unifiedLoginSDK.isAuthenticated();

用户信息管理

// 获取用户信息
unifiedLoginSDK.getUserInfo().then(userInfo => {
  console.log('User info:', userInfo);
});

// 获取用户权限列表
unifiedLoginSDK.getPermissions().then(permissions => {
  console.log('Permissions:', permissions);
});

事件监听

// 监听登录事件
unifiedLoginSDK.on('login', () => {
  console.log('User logged in');
});

// 监听退出事件
unifiedLoginSDK.on('logout', () => {
  console.log('User logged out');
});

// 监听Token过期事件
unifiedLoginSDK.on('tokenExpired', () => {
  console.log('Token expired');
  // 可以在这里执行自定义逻辑,如跳转到登录页
  unifiedLoginSDK.login();
});

// 移除事件监听
const handleLogin = () => console.log('User logged in');
unifiedLoginSDK.on('login', handleLogin);
unifiedLoginSDK.off('login', handleLogin);

框架集成

Vue 2

// main.js
import Vue from 'vue';
import { createVuePlugin } from 'unified-login-sdk';
import App from './App.vue';
import router from './router';

// 创建Vue插件
const vuePlugin = createVuePlugin('localStorage');

// 安装插件
Vue.use(vuePlugin, {
  config: {
    clientId: 'your-client-id',
    authorizationEndpoint: 'https://auth.example.com/authorize',
    tokenEndpoint: 'https://auth.example.com/token',
    userInfoEndpoint: 'https://auth.example.com/userinfo',
    redirectUri: 'https://your-app.example.com/callback'
  }
});

new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

在组件中使用:

<template>
  <div>
    <div v-if="$auth.isAuthenticated()">
      <h1>Welcome, {{ userInfo?.name }}</h1>
      <button @click="logout">Logout</button>
    </div>
    <div v-else>
      <button @click="login">Login</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userInfo: null
    };
  },
  mounted() {
    if (this.$auth.isAuthenticated()) {
      this.getUserInfo();
    } else if (this.$auth.isCallback()) {
      this.handleCallback();
    }
  },
  methods: {
    login() {
      this.$auth.login();
    },
    async logout() {
      await this.$auth.logout();
      window.location.href = '/';
    },
    async getUserInfo() {
      this.userInfo = await this.$auth.getUserInfo();
    },
    async handleCallback() {
      try {
        this.userInfo = await this.$auth.handleCallback();
        window.location.href = '/';
      } catch (error) {
        console.error('Login failed:', error);
      }
    }
  }
};
</script>

Vue 3

// main.js
import { createApp } from 'vue';
import { createVuePlugin } from 'unified-login-sdk';
import App from './App.vue';
import router from './router';

// 创建Vue插件
const vuePlugin = createVuePlugin('localStorage');

const app = createApp(App);

// 安装插件
app.use(vuePlugin, {
  config: {
    clientId: 'your-client-id',
    authorizationEndpoint: 'https://auth.example.com/authorize',
    tokenEndpoint: 'https://auth.example.com/token',
    userInfoEndpoint: 'https://auth.example.com/userinfo',
    redirectUri: 'https://your-app.example.com/callback'
  }
});

app.use(router);
app.mount('#app');

在组件中使用Composition API

<template>
  <div>
    <div v-if="isAuthenticated">
      <h1>Welcome, {{ userInfo?.name }}</h1>
      <button @click="logout">Logout</button>
    </div>
    <div v-else>
      <button @click="login">Login</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, inject } from 'vue';

// 注入SDK实例
const auth = inject('unifiedLogin');
const userInfo = ref(null);
const isAuthenticated = ref(auth.isAuthenticated());

onMounted(() => {
  if (isAuthenticated.value) {
    getUserInfo();
  } else if (auth.isCallback()) {
    handleCallback();
  }
});

const login = () => {
  auth.login();
};

const logout = async () => {
  await auth.logout();
  window.location.href = '/';
};

const getUserInfo = async () => {
  userInfo.value = await auth.getUserInfo();
};

const handleCallback = async () => {
  try {
    userInfo.value = await auth.handleCallback();
    isAuthenticated.value = true;
    window.location.href = '/';
  } catch (error) {
    console.error('Login failed:', error);
  }
};
</script>

## API参考

### 初始化

```typescript
init(config: SDKConfig): void

初始化SDK配置。

登录

login(redirectUri?: string): void

触发登录流程,可选参数redirectUri可覆盖初始化时的配置。

退出登录

logout(): Promise<void>

退出登录清除本地存储的Token和用户信息。

处理授权回调

handleCallback(): Promise<UserInfo>

处理授权回调,获取用户信息。

获取用户信息

getUserInfo(): Promise<UserInfo>

获取用户基本信息。

获取用户权限列表

getPermissions(): Promise<string[]>

获取用户权限列表。

检查是否已认证

isAuthenticated(): boolean

检查用户是否已认证。

获取访问令牌

getAccessToken(): string | null

获取访问令牌。

刷新访问令牌

refreshToken(): Promise<void>

刷新访问令牌。

事件监听

on(event: 'login' | 'logout' | 'tokenExpired', callback: Function): void

监听登录、退出或Token过期事件。

移除事件监听

off(event: 'login' | 'logout' | 'tokenExpired', callback: Function): void

移除事件监听。

配置选项

选项 类型 必填 默认值 描述
clientId string - 客户端ID
clientSecret string - 客户端密钥,某些场景下需要
authorizationEndpoint string - 授权端点URL
tokenEndpoint string - Token端点URL
userInfoEndpoint string - 用户信息端点URL
redirectUri string - 重定向URL
storageType 'localStorage' 'sessionStorage' 'cookie' 'localStorage' Token存储类型
autoRefreshToken boolean true 是否自动刷新Token
permissionsEndpoint string - 权限端点URL
stateLength number 32 状态参数长度
tenantId string - 租户ID会自动添加到请求头中的tenant-id字段

事件处理

事件 描述
login 用户登录成功时触发
logout 用户退出登录时触发
tokenExpired Token过期时触发

路由守卫

Vue路由守卫

// router/index.js
import VueRouter from 'vue-router';
import { Auth } from 'unified-login-sdk';
import { Storage } from 'unified-login-sdk';
import { RouterGuard } from 'unified-login-sdk';

const storage = new Storage('localStorage');
const auth = new Auth(storage);
const routerGuard = new RouterGuard(auth);

const router = new VueRouter({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/protected',
      name: 'Protected',
      component: Protected,
      meta: {
        auth: {
          requiresAuth: true,
          requiredPermissions: ['read:protected']
        }
      }
    }
  ]
});

// 添加路由守卫
router.beforeEach(routerGuard.createVueGuard());

export default router;

错误处理

网络错误处理

try {
  await unifiedLoginSDK.getUserInfo();
} catch (error) {
  if (error.name === 'HttpError') {
    // 处理HTTP错误
    console.error('HTTP Error:', error.status, error.message);
    if (error.status === 401) {
      // 未授权,跳转到登录页
      unifiedLoginSDK.login();
    } else if (error.status === 403) {
      // 权限不足
      window.location.href = '/403';
    }
  } else {
    // 处理其他错误
    console.error('Error:', error.message);
  }
}

Token失效处理

// 监听Token过期事件
unifiedLoginSDK.on('tokenExpired', () => {
  console.log('Token expired');
  // 跳转到登录页
  unifiedLoginSDK.login();
});

最佳实践

  1. 配置安全存储根据项目需求选择合适的存储类型敏感信息建议使用cookie并设置secure和httpOnly标志。

  2. 合理设置Token过期时间根据项目安全性要求设置合适的Token过期时间建议access token过期时间较短refresh token过期时间较长。

  3. 使用路由守卫保护敏感路由:对需要登录或特定权限的路由使用路由守卫进行保护。

  4. 处理网络错误在调用SDK方法时使用try-catch捕获并处理可能的错误。

  5. 监听Token过期事件及时处理Token过期情况避免用户体验下降。

  6. 不要直接暴露clientSecretclientSecret应该只在后端使用前端SDK尽量避免使用clientSecret。

  7. 使用HTTPS确保所有与授权服务器的通信都使用HTTPS避免Token被窃取。

  8. 定期清理存储:在用户退出登录时,确保清理所有相关存储的信息。

浏览器兼容性

  • Chrome (推荐)
  • Firefox
  • Safari
  • Edge

许可证

MIT License