534 lines
12 KiB
Markdown
534 lines
12 KiB
Markdown
|
||
## 安装
|
||
|
||
```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
|