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

534 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 安装
```bash
npm install unified-login-sdk --save
# 或
yarn add unified-login-sdk
```
## 快速开始
### 基本使用
```typescript
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';
});
});
```
## 核心功能
### 初始化配置
```typescript
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管理
```typescript
// 获取访问令牌
const accessToken = unifiedLoginSDK.getAccessToken();
// 刷新令牌
unifiedLoginSDK.refreshToken().then(() => {
console.log('Token refreshed');
}).catch(error => {
console.error('Failed to refresh token:', error);
});
// 检查是否已认证
const isAuthenticated = unifiedLoginSDK.isAuthenticated();
```
### 用户信息管理
```typescript
// 获取用户信息
unifiedLoginSDK.getUserInfo().then(userInfo => {
console.log('User info:', userInfo);
});
// 获取用户权限列表
unifiedLoginSDK.getPermissions().then(permissions => {
console.log('Permissions:', permissions);
});
```
### 事件监听
```typescript
// 监听登录事件
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
```javascript
// 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');
```
在组件中使用:
```vue
<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
```javascript
// 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
```vue
<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配置。
### 登录
```typescript
login(redirectUri?: string): void
```
触发登录流程,可选参数`redirectUri`可覆盖初始化时的配置。
### 退出登录
```typescript
logout(): Promise<void>
```
退出登录清除本地存储的Token和用户信息。
### 处理授权回调
```typescript
handleCallback(): Promise<UserInfo>
```
处理授权回调,获取用户信息。
### 获取用户信息
```typescript
getUserInfo(): Promise<UserInfo>
```
获取用户基本信息。
### 获取用户权限列表
```typescript
getPermissions(): Promise<string[]>
```
获取用户权限列表。
### 检查是否已认证
```typescript
isAuthenticated(): boolean
```
检查用户是否已认证。
### 获取访问令牌
```typescript
getAccessToken(): string | null
```
获取访问令牌。
### 刷新访问令牌
```typescript
refreshToken(): Promise<void>
```
刷新访问令牌。
### 事件监听
```typescript
on(event: 'login' | 'logout' | 'tokenExpired', callback: Function): void
```
监听登录、退出或Token过期事件。
### 移除事件监听
```typescript
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路由守卫
```javascript
// 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;
```
## 错误处理
### 网络错误处理
```typescript
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失效处理
```typescript
// 监听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. **不要直接暴露clientSecret**clientSecret应该只在后端使用前端SDK尽量避免使用clientSecret。
7. **使用HTTPS**确保所有与授权服务器的通信都使用HTTPS避免Token被窃取。
8. **定期清理存储**:在用户退出登录时,确保清理所有相关存储的信息。
## 浏览器兼容性
- Chrome (推荐)
- Firefox
- Safari
- Edge
## 许可证
MIT License