jdk 17
This commit is contained in:
533
sdk/frontend/oauth2-login-sdk/README.md
Normal file
533
sdk/frontend/oauth2-login-sdk/README.md
Normal file
@@ -0,0 +1,533 @@
|
||||
|
||||
## 安装
|
||||
|
||||
```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
|
||||
101
sdk/frontend/oauth2-login-sdk/dist/core/auth.d.ts
vendored
Normal file
101
sdk/frontend/oauth2-login-sdk/dist/core/auth.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
import { EventType, RouterInfo, SDKConfig, UserInfo } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export declare class Auth {
|
||||
private config;
|
||||
private tokenManager;
|
||||
private httpClient;
|
||||
private storage;
|
||||
private eventHandlers;
|
||||
private userInfoCache;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: SDKConfig): void;
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<RouterInfo>;
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
getUserInfo(): UserInfo;
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: EventType, callback: Function): void;
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: EventType, callback: Function): void;
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
private emit;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=auth.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/auth.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/auth.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAC,MAAM,UAAU,CAAC;AAGpE,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AAGzC;;GAEG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,aAAa,CAInB;IACF,OAAO,CAAC,aAAa,CAAyB;IAE9C;;;OAGG;gBACS,OAAO,EAAE,OAAO;IAQ5B;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAM7B,QAAQ,IAAG,MAAM,GAAG,IAAI;IAIxB;;;OAGG;IACG,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC/B,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAYtC;;;OAGG;IACF,WAAW,IAAI,QAAQ;IAOxB;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBxD;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWpD;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBpE;;;;OAIG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAYhE;;;OAGG;IACH,eAAe,IAAI,OAAO;IAK1B;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI9C;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI/C;;;;OAIG;IACH,OAAO,CAAC,IAAI;IAUZ;;;OAGG;IACH,UAAU,IAAI,OAAO;CAGtB"}
|
||||
241
sdk/frontend/oauth2-login-sdk/dist/core/auth.js
vendored
Normal file
241
sdk/frontend/oauth2-login-sdk/dist/core/auth.js
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
import { TokenManager } from './token';
|
||||
import { HttpClient } from './http';
|
||||
import { buildQueryParams, isCallbackUrl, parseQueryParams } from '../utils/url';
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export class Auth {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.config = null;
|
||||
this.eventHandlers = {
|
||||
login: [],
|
||||
logout: [],
|
||||
tokenExpired: []
|
||||
};
|
||||
this.userInfoCache = null;
|
||||
this.storage = storage;
|
||||
// 先创建HttpClient,初始时tokenManager为undefined
|
||||
this.httpClient = new HttpClient(() => this.tokenManager.getToken() || null);
|
||||
// 然后创建TokenManager
|
||||
this.tokenManager = new TokenManager(storage);
|
||||
}
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config) {
|
||||
this.config = config;
|
||||
// 设置租户ID到HTTP客户端
|
||||
this.httpClient.setTenantId(config.tenantId);
|
||||
}
|
||||
getToken() {
|
||||
return this.tokenManager.getToken();
|
||||
}
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
async login(redirectUri) {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp';
|
||||
const basepath = this.config.basepath || '';
|
||||
const path = `${basepath}/oauth2/authorization/${registrationId}`;
|
||||
const tokenResponse = await this.httpClient.get(path, { needAuth: false });
|
||||
const redirect = tokenResponse.data.redirect_url;
|
||||
const params = parseQueryParams(redirect);
|
||||
this.storage.set(params.state, window.location.href);
|
||||
window.location.href = redirect;
|
||||
}
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
async logout() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
// 清除本地存储的Token和用户信息
|
||||
this.tokenManager.clearToken();
|
||||
this.userInfoCache = null;
|
||||
this.storage.remove('userInfo');
|
||||
const basepath = this.config.basepath || '';
|
||||
await this.httpClient.post(`${basepath}/logout`, null, { needAuth: true });
|
||||
// 触发退出事件
|
||||
this.emit('logout');
|
||||
window.location.href = this.config.idpLogoutUrl + '?redirect=' + this.config.homePage;
|
||||
}
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
async handleCallback() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const params = parseQueryParams();
|
||||
// 检查是否有错误
|
||||
if (params.error) {
|
||||
throw new Error(`Authorization error: ${params.error} - ${params.error_description || ''}`);
|
||||
}
|
||||
// 检查是否有授权码
|
||||
if (!params.code) {
|
||||
throw new Error('Authorization code not found');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp';
|
||||
const basepath = this.config.basepath || '';
|
||||
const callback = `${basepath}/login/oauth2/code/${registrationId}${buildQueryParams(params)}`;
|
||||
const tokenResponse = await this.httpClient.get(callback, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
needAuth: false
|
||||
});
|
||||
// 触发登录事件
|
||||
this.emit('login');
|
||||
this.storage.set('userInfo', tokenResponse.data.data);
|
||||
this.tokenManager.saveToken(tokenResponse.headers['authorization'] || tokenResponse.headers['Authorization']);
|
||||
let url = this.config.homePage;
|
||||
if (params.state) {
|
||||
url = this.storage.get(params.state) || url;
|
||||
}
|
||||
window.location.href = url;
|
||||
}
|
||||
async getRoutes() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const basepath = this.config.basepath || '';
|
||||
const tokenResponse = await this.httpClient.get(`${basepath}/idp/routes`, { needAuth: true });
|
||||
if (tokenResponse.status === 401) {
|
||||
await this.logout();
|
||||
}
|
||||
return tokenResponse.data.data;
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
getUserInfo() {
|
||||
return this.storage.get("userInfo");
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
async hasRole(role) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles || [];
|
||||
if (Array.isArray(role)) {
|
||||
// 检查是否有任一角色
|
||||
return role.some(r => roleCodes.includes(r));
|
||||
}
|
||||
// 检查是否有单个角色
|
||||
return roleCodes.includes(role);
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
async hasAllRoles(roles) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles || [];
|
||||
// 检查是否有所有角色
|
||||
return roles.every(r => roleCodes.includes(r));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
async hasPermission(permission) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const permissions = userInfo.permissions || [];
|
||||
if (Array.isArray(permission)) {
|
||||
// 检查是否有任一权限
|
||||
return permission.some(p => permissions.includes(p));
|
||||
}
|
||||
// 检查是否有单个权限
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
async hasAllPermissions(permissions) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const userPermissions = userInfo.permissions || [];
|
||||
// 检查是否有所有权限
|
||||
return permissions.every(p => userPermissions.includes(p));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated() {
|
||||
// 检查Token是否存在且未过期
|
||||
return !!this.tokenManager.getToken();
|
||||
}
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event, callback) {
|
||||
this.eventHandlers[event].push(callback);
|
||||
}
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event, callback) {
|
||||
this.eventHandlers[event] = this.eventHandlers[event].filter(handler => handler !== callback);
|
||||
}
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
emit(event, data) {
|
||||
this.eventHandlers[event].forEach(handler => {
|
||||
try {
|
||||
handler(data);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error in ${event} event handler:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback() {
|
||||
return isCallbackUrl();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=auth.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/auth.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/auth.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
155
sdk/frontend/oauth2-login-sdk/dist/core/http.d.ts
vendored
Normal file
155
sdk/frontend/oauth2-login-sdk/dist/core/http.d.ts
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信
|
||||
*/
|
||||
/**
|
||||
* HTTP请求方法类型
|
||||
*/
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
/**
|
||||
* HTTP请求选项
|
||||
*/
|
||||
export interface HttpRequestOptions {
|
||||
/** 请求方法 */
|
||||
method: HttpMethod;
|
||||
/** 请求URL */
|
||||
url: string;
|
||||
/** 请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 请求体 */
|
||||
body?: any;
|
||||
/** 是否需要认证 */
|
||||
needAuth?: boolean;
|
||||
}
|
||||
/**
|
||||
* HTTP响应类型
|
||||
*/
|
||||
export interface HttpResponse<T = any> {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 响应体 */
|
||||
data: T;
|
||||
/** 响应头 */
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export declare class HttpError extends Error {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 错误数据 */
|
||||
data: any;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
*/
|
||||
constructor(message: string, status: number, statusText: string, data: any);
|
||||
}
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export declare class HttpClient {
|
||||
private tokenGetter?;
|
||||
private tenantId?;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param logout
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
constructor(tokenGetter?: () => string | null);
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter: () => string | null): void;
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId?: string): void;
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* 处理响应数据
|
||||
* @param response 响应对象
|
||||
* @param responseData 响应数据
|
||||
* @returns HttpResponse<T> 处理后的响应
|
||||
*/
|
||||
private handleResponse;
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param responseData 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
private isBusinessResponse;
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @param responseData 响应数据
|
||||
* @returns string 错误信息
|
||||
*/
|
||||
private getErrorMessage;
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
get<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
post<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
put<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
delete<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
patch<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
private parseResponse;
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
private parseHeaders;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=http.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/http.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/http.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,WAAW;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU;IACV,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,aAAa;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACnC,UAAU;IACV,MAAM,EAAE,MAAM,CAAC;IACf,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU;IACV,IAAI,EAAE,CAAC,CAAC;IACR,UAAU;IACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,UAAU;IACH,MAAM,EAAE,MAAM,CAAC;IACtB,WAAW;IACJ,UAAU,EAAE,MAAM,CAAC;IAC1B,WAAW;IACJ,IAAI,EAAE,GAAG,CAAC;IAEjB;;;;;;OAMG;gBACS,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAO3E;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAC,CAAS;IAE1B;;;;OAIG;gBACS,WAAW,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI;IAK7C;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI;IAItD;;;OAGG;IACH,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAIpC;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IA0F7E;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmCtB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;OAKG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAQ/G;;;;;;OAMG;IACG,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IASrI;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IASpI;;;;;OAKG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAQlH;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAStI;;;;OAIG;YACW,aAAa;IAY3B;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAOrB"}
|
||||
274
sdk/frontend/oauth2-login-sdk/dist/core/http.js
vendored
Normal file
274
sdk/frontend/oauth2-login-sdk/dist/core/http.js
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信
|
||||
*/
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
*/
|
||||
constructor(message, status, statusText, data) {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export class HttpClient {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param logout
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
constructor(tokenGetter) {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter) {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async request(options) {
|
||||
const { method, url, headers = {}, body, needAuth = true } = options;
|
||||
// 构建请求头
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
};
|
||||
// 添加认证头
|
||||
const addAuthHeader = () => {
|
||||
if (needAuth && this.tokenGetter) {
|
||||
const token = this.tokenGetter();
|
||||
if (token) {
|
||||
requestHeaders.Authorization = `${token}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 添加租户ID头
|
||||
if (this.tenantId) {
|
||||
requestHeaders['tenant-id'] = this.tenantId;
|
||||
}
|
||||
addAuthHeader();
|
||||
// 构建请求配置
|
||||
const fetchOptions = {
|
||||
method,
|
||||
headers: requestHeaders,
|
||||
credentials: 'include' // 包含cookie
|
||||
};
|
||||
// 添加请求体
|
||||
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
}
|
||||
try {
|
||||
// 发送请求
|
||||
const response = await fetch(url, fetchOptions);
|
||||
const responseData = await this.parseResponse(response);
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
// 如果是401错误,尝试刷新Token并重试
|
||||
if (response.status === 401) {
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: '',
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
// 其他错误,直接抛出
|
||||
const errorMsg = this.getErrorMessage(responseData);
|
||||
throw new HttpError(errorMsg, response.status, response.statusText, responseData);
|
||||
}
|
||||
// 处理成功响应的业务逻辑
|
||||
return this.handleResponse(response, responseData);
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
throw error;
|
||||
}
|
||||
// 网络错误或其他错误
|
||||
throw new HttpError(error instanceof Error ? error.message : 'Network Error', 0, 'Network Error', null);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理响应数据
|
||||
* @param response 响应对象
|
||||
* @param responseData 响应数据
|
||||
* @returns HttpResponse<T> 处理后的响应
|
||||
*/
|
||||
handleResponse(response, responseData) {
|
||||
// 检查是否为业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
// 业务响应结构:{ code, msg, data }
|
||||
const { code, msg, data } = responseData;
|
||||
// 检查业务状态码
|
||||
if (code !== 0 && code !== 200 && code !== '0' && code !== '200') {
|
||||
// 业务错误,抛出HttpError
|
||||
throw new HttpError(msg || `Business Error: ${code}`, response.status, response.statusText, responseData);
|
||||
}
|
||||
// 业务成功,返回data字段作为实际数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: data,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
// 非业务响应结构,直接返回原始数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: responseData,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param responseData 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
isBusinessResponse(responseData) {
|
||||
return typeof responseData === 'object' &&
|
||||
responseData !== null &&
|
||||
('code' in responseData) &&
|
||||
('msg' in responseData) &&
|
||||
('data' in responseData);
|
||||
}
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @param responseData 响应数据
|
||||
* @returns string 错误信息
|
||||
*/
|
||||
getErrorMessage(responseData) {
|
||||
// 如果是业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
return responseData.msg || `Business Error: ${responseData.code}`;
|
||||
}
|
||||
// 其他错误结构
|
||||
return responseData.message || responseData.error || `HTTP Error`;
|
||||
}
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async get(url, options) {
|
||||
return this.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async post(url, body, options) {
|
||||
return this.request({
|
||||
method: 'POST',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async put(url, body, options) {
|
||||
return this.request({
|
||||
method: 'PUT',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async delete(url, options) {
|
||||
return this.request({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async patch(url, body, options) {
|
||||
return this.request({
|
||||
method: 'PATCH',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
async parseResponse(response) {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
}
|
||||
else if (contentType.includes('text/')) {
|
||||
return response.text();
|
||||
}
|
||||
else {
|
||||
return response.blob();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
parseHeaders(headers) {
|
||||
const result = {};
|
||||
headers.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=http.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/http.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/http.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
32
sdk/frontend/oauth2-login-sdk/dist/core/token.d.ts
vendored
Normal file
32
sdk/frontend/oauth2-login-sdk/dist/core/token.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
import { Storage } from '../utils/storage';
|
||||
/**
|
||||
* Token管理类
|
||||
*/
|
||||
export declare class TokenManager {
|
||||
private storage;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param httpClient HTTP客户端实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
saveToken(tokenInfo: string): void;
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns TokenInfo | null Token信息
|
||||
*/
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken(): void;
|
||||
}
|
||||
//# sourceMappingURL=token.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/token.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/token.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/core/token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAU;IAEzB;;;;OAIG;gBACS,OAAO,EAAE,OAAO;IAI5B;;;OAGG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACH,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
|
||||
38
sdk/frontend/oauth2-login-sdk/dist/core/token.js
vendored
Normal file
38
sdk/frontend/oauth2-login-sdk/dist/core/token.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
/**
|
||||
* Token管理类
|
||||
*/
|
||||
export class TokenManager {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param httpClient HTTP客户端实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
saveToken(tokenInfo) {
|
||||
this.storage.set('token', tokenInfo);
|
||||
}
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns TokenInfo | null Token信息
|
||||
*/
|
||||
getToken() {
|
||||
return this.storage.get('token');
|
||||
}
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken() {
|
||||
this.storage.remove('token');
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=token.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/core/token.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/core/token.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/core/token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,OAAO,YAAY;IAGvB;;;;OAIG;IACH,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,SAAiB;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACF"}
|
||||
55
sdk/frontend/oauth2-login-sdk/dist/guards/router.d.ts
vendored
Normal file
55
sdk/frontend/oauth2-login-sdk/dist/guards/router.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
/**
|
||||
* 路由守卫选项
|
||||
*/
|
||||
export interface RouterGuardOptions {
|
||||
/**
|
||||
* 是否需要登录
|
||||
*/
|
||||
requiresAuth?: boolean;
|
||||
/**
|
||||
* 需要的权限列表
|
||||
*/
|
||||
requiredPermissions?: string[];
|
||||
/**
|
||||
* 登录后重定向的URL
|
||||
*/
|
||||
redirectUri?: string;
|
||||
/**
|
||||
* 权限不足时重定向的URL
|
||||
*/
|
||||
unauthorizedRedirectUri?: string;
|
||||
}
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export declare class RouterGuard {
|
||||
private auth;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth: Auth);
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<boolean> 是否通过权限检查
|
||||
*/
|
||||
check(options: RouterGuardOptions): Promise<boolean>;
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns 路由守卫函数
|
||||
*/
|
||||
createVueGuard(): (to: any, from: any, next: any) => Promise<void>;
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
hasPermission(permissions: string | string[]): Promise<boolean>;
|
||||
}
|
||||
//# sourceMappingURL=router.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/guards/router.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/guards/router.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/guards/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAO;IAEnB;;;OAGG;gBACS,IAAI,EAAE,IAAI;IAItB;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAmC1D;;;OAGG;IACH,cAAc,KACE,IAAI,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM,GAAG;IAgB7C;;;;OAIG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAoBtE"}
|
||||
89
sdk/frontend/oauth2-login-sdk/dist/guards/router.js
vendored
Normal file
89
sdk/frontend/oauth2-login-sdk/dist/guards/router.js
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export class RouterGuard {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<boolean> 是否通过权限检查
|
||||
*/
|
||||
async check(options) {
|
||||
const { requiresAuth = true, requiredPermissions = [] } = options;
|
||||
// 检查是否需要登录
|
||||
if (requiresAuth) {
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
// 未认证,跳转到登录页
|
||||
this.auth.login(options.redirectUri);
|
||||
return false;
|
||||
}
|
||||
// 检查是否需要权限
|
||||
if (requiredPermissions.length > 0) {
|
||||
// 获取用户权限
|
||||
const userPermissions = [''];
|
||||
// 检查是否拥有所有需要的权限
|
||||
const hasPermission = requiredPermissions.every(permission => userPermissions.includes(permission));
|
||||
if (!hasPermission) {
|
||||
// 权限不足,跳转到权限不足页
|
||||
if (options.unauthorizedRedirectUri) {
|
||||
window.location.href = options.unauthorizedRedirectUri;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns 路由守卫函数
|
||||
*/
|
||||
createVueGuard() {
|
||||
return async (to, from, next) => {
|
||||
var _a;
|
||||
// 从路由元信息中获取守卫选项
|
||||
const options = ((_a = to.meta) === null || _a === void 0 ? void 0 : _a.auth) || {};
|
||||
try {
|
||||
const allowed = await this.check(options);
|
||||
if (allowed) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
async hasPermission(permissions) {
|
||||
if (!permissions) {
|
||||
return true;
|
||||
}
|
||||
const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
// 获取用户权限
|
||||
const userPermissions = [''];
|
||||
// 检查是否拥有所有需要的权限
|
||||
return requiredPermissions.every(permission => userPermissions.includes(permission));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=router.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/guards/router.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/guards/router.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/guards/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0BH;;GAEG;AACH,MAAM,OAAO,WAAW;IAGtB;;;OAGG;IACH,YAAY,IAAU;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,OAA2B;QACrC,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,mBAAmB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;QAElE,WAAW;QACX,IAAI,YAAY,EAAE,CAAC;YACjB,UAAU;YACV,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;gBACjC,aAAa;gBACb,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBACrC,OAAO,KAAK,CAAC;YACf,CAAC;YAED,WAAW;YACX,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnC,SAAS;gBACT,MAAM,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC;gBAE7B,gBAAgB;gBAChB,MAAM,aAAa,GAAG,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAC3D,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CACrC,CAAC;gBAEF,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,gBAAgB;oBAChB,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;wBACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,uBAAuB,CAAC;oBACzD,CAAC;oBACD,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,OAAO,KAAK,EAAE,EAAO,EAAE,IAAS,EAAE,IAAS,EAAE,EAAE;;YAC7C,gBAAgB;YAChB,MAAM,OAAO,GAAuB,CAAA,MAAA,EAAE,CAAC,IAAI,0CAAE,IAAI,KAAI,EAAE,CAAC;YAExD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1C,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,EAAE,CAAC;gBACT,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;gBAC3C,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa,CAAC,WAA8B;QAChD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,mBAAmB,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAErF,UAAU;QACV,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,SAAS;QACT,MAAM,eAAe,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5B,gBAAgB;QAChB,OAAO,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAC5C,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CACrC,CAAC;IACJ,CAAC;CACF"}
|
||||
20
sdk/frontend/oauth2-login-sdk/dist/index.d.ts
vendored
Normal file
20
sdk/frontend/oauth2-login-sdk/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 统一登录SDK入口文件
|
||||
* 支持OAuth2授权码模式,提供完整的Token管理和用户信息管理功能
|
||||
*/
|
||||
export { Auth } from './core/auth';
|
||||
export { TokenManager } from './core/token';
|
||||
export { HttpClient, HttpError } from './core/http';
|
||||
export { Storage } from './utils/storage';
|
||||
export { RouterGuard, RouterGuardOptions } from './guards/router';
|
||||
export { generateRandomString, parseQueryParams, buildQueryParams, generateAuthorizationUrl, isCallbackUrl } from './utils/url';
|
||||
export * from './types';
|
||||
export { VuePlugin, createVuePlugin } from './plugins/vue';
|
||||
import { UnifiedLoginSDK } from './types';
|
||||
/**
|
||||
* 默认导出的SDK实例
|
||||
*/
|
||||
export declare const unifiedLoginSDK: UnifiedLoginSDK;
|
||||
export default unifiedLoginSDK;
|
||||
export declare const version = "1.0.0";
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/index.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,aAAa,EACd,MAAM,aAAa,CAAC;AAGrB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,EAAa,eAAe,EAAE,MAAM,SAAS,CAAC;AAUrD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,eA8C7B,CAAC;AAGF,eAAe,eAAe,CAAC;AAG/B,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
||||
1192
sdk/frontend/oauth2-login-sdk/dist/index.esm.js
vendored
Normal file
1192
sdk/frontend/oauth2-login-sdk/dist/index.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
sdk/frontend/oauth2-login-sdk/dist/index.esm.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/index.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1211
sdk/frontend/oauth2-login-sdk/dist/index.js
vendored
Normal file
1211
sdk/frontend/oauth2-login-sdk/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
sdk/frontend/oauth2-login-sdk/dist/index.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
56
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.d.ts
vendored
Normal file
56
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.d.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
import { SDKConfig } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
/**
|
||||
* Vue插件选项
|
||||
*/
|
||||
export interface VuePluginOptions {
|
||||
/**
|
||||
* SDK配置
|
||||
*/
|
||||
config: SDKConfig;
|
||||
/**
|
||||
* 插件名称,默认'unifiedLogin'
|
||||
*/
|
||||
pluginName?: string;
|
||||
}
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export declare class VuePlugin {
|
||||
private auth;
|
||||
private routerGuard;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app: any, options: VuePluginOptions): void;
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth(): Auth;
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard(): RouterGuard;
|
||||
}
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export declare function createVuePlugin(storageType?: 'localStorage' | 'sessionStorage' | 'cookie'): VuePlugin;
|
||||
//# sourceMappingURL=vue.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vue.d.ts","sourceRoot":"","sources":["../../src/plugins/vue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,WAAW,CAAc;IAEjC;;;OAGG;gBACS,OAAO,EAAE,OAAO;IAK5B;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAkDlD;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf;;;OAGG;IACH,cAAc,IAAI,WAAW;CAG9B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAGrG"}
|
||||
93
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.js
vendored
Normal file
93
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.js
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export class VuePlugin {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.auth = new Auth(storage);
|
||||
this.routerGuard = new RouterGuard(this.auth);
|
||||
}
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app, options) {
|
||||
const { config, pluginName = 'unifiedLogin' } = options;
|
||||
// 初始化SDK
|
||||
this.auth.init(config);
|
||||
// 判断是Vue 2还是Vue 3
|
||||
const isVue3 = typeof app.config !== 'undefined';
|
||||
if (isVue3) {
|
||||
// Vue 3
|
||||
// 在全局属性上挂载SDK实例
|
||||
app.config.globalProperties[`${pluginName}`] = this.auth;
|
||||
app.config.globalProperties.$auth = this.auth; // 兼容简写
|
||||
// 提供Vue组件内的注入
|
||||
app.provide(pluginName, this.auth);
|
||||
app.provide('auth', this.auth); // 兼容简写
|
||||
// 处理路由守卫
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Vue 2
|
||||
// 在Vue实例上挂载SDK实例
|
||||
app.prototype[`${pluginName}`] = this.auth;
|
||||
app.prototype.$auth = this.auth; // 兼容简写
|
||||
// 全局混入
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth() {
|
||||
return this.auth;
|
||||
}
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard() {
|
||||
return this.routerGuard;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export function createVuePlugin(storageType) {
|
||||
const storage = new Storage(storageType);
|
||||
return new VuePlugin(storage);
|
||||
}
|
||||
//# sourceMappingURL=vue.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/plugins/vue.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vue.js","sourceRoot":"","sources":["../../src/plugins/vue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAgB/C;;GAEG;AACH,MAAM,OAAO,SAAS;IAIpB;;;OAGG;IACH,YAAY,OAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAQ,EAAE,OAAyB;QACzC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,cAAc,EAAE,GAAG,OAAO,CAAC;QAExD,SAAS;QACT,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEvB,kBAAkB;QAClB,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAEjD,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ;YACR,gBAAgB;YAChB,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YACzD,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO;YAEtD,cAAc;YACd,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;YAEvC,SAAS;YACT,GAAG,CAAC,KAAK,CAAC;gBACR,YAAY;oBACV,gBAAgB;oBAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;wBACpC,WAAW;wBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,QAAQ;YACR,iBAAiB;YACjB,GAAG,CAAC,SAAS,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO;YAExC,OAAO;YACP,GAAG,CAAC,KAAK,CAAC;gBACR,YAAY;oBACV,gBAAgB;oBAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;wBACpC,WAAW;wBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAA0D;IACxF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC"}
|
||||
39
sdk/frontend/oauth2-login-sdk/dist/types/config.d.ts
vendored
Normal file
39
sdk/frontend/oauth2-login-sdk/dist/types/config.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* SDK配置选项
|
||||
*/
|
||||
export interface SDKConfig {
|
||||
/** 客户端ID */
|
||||
clientId: string;
|
||||
/** 注册id **/
|
||||
registrationId: string;
|
||||
/** 后端basepath路径*/
|
||||
basepath: string;
|
||||
/** 存储类型,默认localStorage */
|
||||
storageType?: 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
idpLogoutUrl: string;
|
||||
homePage: string;
|
||||
/** 租户ID(可选) */
|
||||
tenantId?: string;
|
||||
}
|
||||
/**
|
||||
* Token信息
|
||||
*/
|
||||
export interface TokenInfo {
|
||||
/** 访问令牌 */
|
||||
accessToken: string;
|
||||
/** 刷新令牌 */
|
||||
refreshToken: string;
|
||||
/** 令牌类型,默认Bearer */
|
||||
tokenType?: string;
|
||||
/** 访问令牌过期时间(秒) */
|
||||
expiresIn: number;
|
||||
/** 刷新令牌过期时间(秒) */
|
||||
refreshExpiresIn?: number;
|
||||
/** 令牌颁发时间戳 */
|
||||
issuedAt: number;
|
||||
}
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
export type EventType = 'login' | 'logout' | 'tokenExpired';
|
||||
//# sourceMappingURL=config.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/config.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/config.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;IAC3D,YAAY,EAAC,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC"}
|
||||
2
sdk/frontend/oauth2-login-sdk/dist/types/config.js
vendored
Normal file
2
sdk/frontend/oauth2-login-sdk/dist/types/config.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=config.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/config.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/config.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":""}
|
||||
80
sdk/frontend/oauth2-login-sdk/dist/types/index.d.ts
vendored
Normal file
80
sdk/frontend/oauth2-login-sdk/dist/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
/**
|
||||
* 统一登录SDK接口
|
||||
*/
|
||||
export interface UnifiedLoginSDK {
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: import('./config').SDKConfig): void;
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<import('./user').RouterInfo>;
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
getUserInfo(): import('./user').UserInfo;
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: import('./config').EventType, callback: Function): void;
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: import('./config').EventType, callback: Function): void;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/index.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AAEvB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,SAAS,GAAG,IAAI,CAAC;IAEjD,QAAQ,IAAG,MAAM,GAAC,IAAI,CAAA;IACtB;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,IAAI,OAAO,CAAC,OAAO,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD;;;OAGG;IACH,WAAW,IAAI,OAAO,QAAQ,EAAE,QAAQ,CAAC;IAEzC;;;OAGG;IACH,eAAe,IAAI,OAAO,CAAC;IAE3B;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/C;;;;OAIG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/D;;;;OAIG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,OAAO,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAElE;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,OAAO,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAEnE;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC;CACvB"}
|
||||
3
sdk/frontend/oauth2-login-sdk/dist/types/index.js
vendored
Normal file
3
sdk/frontend/oauth2-login-sdk/dist/types/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/index.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC"}
|
||||
83
sdk/frontend/oauth2-login-sdk/dist/types/user.d.ts
vendored
Normal file
83
sdk/frontend/oauth2-login-sdk/dist/types/user.d.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 菜单信息
|
||||
*/
|
||||
export interface RouterInfo {
|
||||
/** 菜单名称 */
|
||||
name: string;
|
||||
/** 菜单路径 */
|
||||
path?: string;
|
||||
hidden: boolean;
|
||||
redirect: string;
|
||||
query: string;
|
||||
alwaysShow: boolean;
|
||||
/** 菜单组件 */
|
||||
component?: string;
|
||||
meta: MetaVo;
|
||||
children: RouterInfo;
|
||||
}
|
||||
export interface MetaVo {
|
||||
/**
|
||||
* 设置该路由在侧边栏和面包屑中展示的名字
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
*/
|
||||
icon: string;
|
||||
/**
|
||||
* 设置为true,则不会被 <keep-alive>缓存
|
||||
*/
|
||||
noCache: boolean;
|
||||
/**
|
||||
* 内链地址(http(s)://开头)
|
||||
*/
|
||||
link: string;
|
||||
}
|
||||
/**
|
||||
* 用户基本信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 姓名 */
|
||||
nickName: string;
|
||||
/** 邮箱 */
|
||||
currentDeptId: string;
|
||||
/** 部门 */
|
||||
userDepts?: UserDept[];
|
||||
/** 岗位 */
|
||||
userPost?: UserPost[];
|
||||
/** 性别 */
|
||||
sex: string;
|
||||
/** 用户角色 */
|
||||
roles?: string[];
|
||||
/** 权限列表 */
|
||||
permissions?: string[];
|
||||
dataPermission: DataPermission;
|
||||
}
|
||||
export interface DataPermission {
|
||||
allowAll: boolean;
|
||||
onlySelf: boolean;
|
||||
deptList?: string[];
|
||||
areas?: string[];
|
||||
}
|
||||
export interface UserDept {
|
||||
postCode: string;
|
||||
postId: bigint;
|
||||
postName: string;
|
||||
postSort: bigint;
|
||||
remark: string;
|
||||
status: bigint;
|
||||
}
|
||||
export interface UserPost {
|
||||
ancestors: string;
|
||||
deptId: bigint;
|
||||
deptName: string;
|
||||
leader: string;
|
||||
orderNum: bigint;
|
||||
parentId: bigint;
|
||||
status: bigint;
|
||||
}
|
||||
//# sourceMappingURL=user.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/user.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/user.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAC,MAAM,CAAC;IACZ,QAAQ,EAAE,UAAU,CAAC;CACtB;AACD,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,KAAK,EAAC,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAC,MAAM,CAAC;IAEZ;;OAEG;IACH,OAAO,EAAC,OAAO,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAC,MAAM,CAAC;CACb;AAGD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS;IACT,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS;IACT,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,SAAS;IACT,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,cAAc,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAC,MAAM,EAAE,CAAA;CAChB;AACD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;IACb,QAAQ,EAAC,MAAM,CAAA;IACf,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;IACb,MAAM,EAAC,MAAM,CAAA;CACd;AACD,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf"}
|
||||
2
sdk/frontend/oauth2-login-sdk/dist/types/user.js
vendored
Normal file
2
sdk/frontend/oauth2-login-sdk/dist/types/user.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=user.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/types/user.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/types/user.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":""}
|
||||
108
sdk/frontend/oauth2-login-sdk/dist/utils/storage.d.ts
vendored
Normal file
108
sdk/frontend/oauth2-login-sdk/dist/utils/storage.d.ts
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
type StorageType = 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export declare class Storage {
|
||||
private storageType;
|
||||
private prefix;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType?: StorageType, prefix?: string);
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key: string, value: any, options?: {
|
||||
expires?: number;
|
||||
path?: string;
|
||||
domain?: string;
|
||||
secure?: boolean;
|
||||
}): void;
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key: string): any;
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key: string): void;
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable(): boolean;
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
private setLocalStorage;
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
private getLocalStorage;
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
private removeLocalStorage;
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
private clearLocalStorage;
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
private isLocalStorageAvailable;
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
private setSessionStorage;
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
private getSessionStorage;
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
private removeSessionStorage;
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
private clearSessionStorage;
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
private isSessionStorageAvailable;
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
private setCookie;
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
private getCookie;
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
private removeCookie;
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
private clearCookie;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=storage.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/utils/storage.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/utils/storage.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/utils/storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,WAAW,GAAG,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;AAEhE;;GAEG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAS;IAEvB;;;;OAIG;gBACS,WAAW,GAAE,WAA4B,EAAE,MAAM,GAAE,MAAyB;IAKxF;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAiBpH;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IA+BrB;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAgBzB;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb;;;OAGG;IACH,WAAW,IAAI,OAAO;IAmBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAgBjC;;OAEG;IACH,OAAO,CAAC,SAAS;IAsCjB;;OAEG;IACH,OAAO,CAAC,SAAS;IAsBjB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,WAAW;CAcpB"}
|
||||
316
sdk/frontend/oauth2-login-sdk/dist/utils/storage.js
vendored
Normal file
316
sdk/frontend/oauth2-login-sdk/dist/utils/storage.js
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export class Storage {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType = 'localStorage', prefix = 'unified_login_') {
|
||||
this.storageType = storageType;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key, value, options) {
|
||||
const fullKey = this.prefix + key;
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.setLocalStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.setSessionStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.setCookie(fullKey, stringValue, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key) {
|
||||
const fullKey = this.prefix + key;
|
||||
let value;
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
value = this.getLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
value = this.getSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
value = this.getCookie(fullKey);
|
||||
break;
|
||||
default:
|
||||
value = null;
|
||||
}
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (e) {
|
||||
// 如果不是JSON,直接返回字符串
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key) {
|
||||
const fullKey = this.prefix + key;
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.removeLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.removeSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.removeCookie(fullKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear() {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.clearLocalStorage();
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.clearSessionStorage();
|
||||
break;
|
||||
case 'cookie':
|
||||
this.clearCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable() {
|
||||
try {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
return this.isLocalStorageAvailable();
|
||||
case 'sessionStorage':
|
||||
return this.isSessionStorageAvailable();
|
||||
case 'cookie':
|
||||
return typeof document !== 'undefined';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ localStorage 操作 ------------------------
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
setLocalStorage(key, value) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
getLocalStorage(key) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
removeLocalStorage(key) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
clearLocalStorage() {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
localStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
isLocalStorageAvailable() {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
localStorage.setItem(testKey, testKey);
|
||||
localStorage.removeItem(testKey);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ sessionStorage 操作 ------------------------
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
setSessionStorage(key, value) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
getSessionStorage(key) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
removeSessionStorage(key) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
clearSessionStorage() {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
for (let i = 0; i < sessionStorage.length; i++) {
|
||||
const key = sessionStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
sessionStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
isSessionStorageAvailable() {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
sessionStorage.setItem(testKey, testKey);
|
||||
sessionStorage.removeItem(testKey);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ cookie 操作 ------------------------
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
setCookie(key, value, options) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
let cookieString = `${key}=${encodeURIComponent(value)}`;
|
||||
if (options) {
|
||||
// 设置过期时间(秒)
|
||||
if (options.expires) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + options.expires * 1000);
|
||||
cookieString += `; expires=${date.toUTCString()}`;
|
||||
}
|
||||
// 设置路径
|
||||
if (options.path) {
|
||||
cookieString += `; path=${options.path}`;
|
||||
}
|
||||
// 设置域名
|
||||
if (options.domain) {
|
||||
cookieString += `; domain=${options.domain}`;
|
||||
}
|
||||
// 设置secure
|
||||
if (options.secure) {
|
||||
cookieString += '; secure';
|
||||
}
|
||||
}
|
||||
document.cookie = cookieString;
|
||||
}
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
getCookie(key) {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const name = `${key}=`;
|
||||
const decodedCookie = decodeURIComponent(document.cookie);
|
||||
const ca = decodedCookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
removeCookie(key) {
|
||||
this.setCookie(key, '', { expires: -1 });
|
||||
}
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
clearCookie() {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const eqPos = cookie.indexOf('=');
|
||||
const key = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (key.startsWith(this.prefix)) {
|
||||
this.removeCookie(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=storage.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/utils/storage.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/utils/storage.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
55
sdk/frontend/oauth2-login-sdk/dist/utils/url.d.ts
vendored
Normal file
55
sdk/frontend/oauth2-login-sdk/dist/utils/url.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export declare function generateRandomString(length?: number): string;
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export declare function parseQueryParams(url?: string): Record<string, string>;
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export declare function buildQueryParams(params: Record<string, any>): string;
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export declare function generateAuthorizationUrl(authorizationEndpoint: string, clientId: string, redirectUri: string, options?: {
|
||||
responseType?: string;
|
||||
scope?: string;
|
||||
state?: string;
|
||||
[key: string]: any;
|
||||
}): string;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export declare function isCallbackUrl(url?: string): boolean;
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export declare function getPathname(url?: string): string;
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export declare function getHostname(url?: string): string;
|
||||
//# sourceMappingURL=url.d.ts.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/utils/url.d.ts.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/utils/url.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAOhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgB3F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAQpE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,qBAAqB,EAAE,MAAM,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IACR,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,GACA,MAAM,CAmBR;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAA6B,GAAG,OAAO,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAGtE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAGtE"}
|
||||
100
sdk/frontend/oauth2-login-sdk/dist/utils/url.js
vendored
Normal file
100
sdk/frontend/oauth2-login-sdk/dist/utils/url.js
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export function generateRandomString(length = 32) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export function parseQueryParams(url = window.location.href) {
|
||||
const params = {};
|
||||
const queryString = url.split('?')[1];
|
||||
if (!queryString) {
|
||||
return params;
|
||||
}
|
||||
const pairs = queryString.split('&');
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=');
|
||||
if (key) {
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export function buildQueryParams(params) {
|
||||
const pairs = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return pairs.length ? `?${pairs.join('&')}` : '';
|
||||
}
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export function generateAuthorizationUrl(authorizationEndpoint, clientId, redirectUri, options) {
|
||||
const { responseType = 'code', scope, state = generateRandomString(32), ...extraParams } = options || {};
|
||||
const params = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: responseType,
|
||||
state,
|
||||
...(scope ? { scope } : {}),
|
||||
...extraParams
|
||||
};
|
||||
const queryString = buildQueryParams(params);
|
||||
return `${authorizationEndpoint}${queryString}`;
|
||||
}
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export function isCallbackUrl(url = window.location.href) {
|
||||
const params = parseQueryParams(url);
|
||||
return !!params.code || !!params.error;
|
||||
}
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export function getPathname(url = window.location.href) {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.pathname;
|
||||
}
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export function getHostname(url = window.location.href) {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname;
|
||||
}
|
||||
//# sourceMappingURL=url.js.map
|
||||
1
sdk/frontend/oauth2-login-sdk/dist/utils/url.js.map
vendored
Normal file
1
sdk/frontend/oauth2-login-sdk/dist/utils/url.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE;IACtD,MAAM,KAAK,GAAG,gEAAgE,CAAC;IAC/E,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IACjE,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAA2B;IAC1D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACtC,qBAA6B,EAC7B,QAAgB,EAChB,WAAmB,EACnB,OAKC;IAED,MAAM,EACJ,YAAY,GAAG,MAAM,EACrB,KAAK,EACL,KAAK,GAAG,oBAAoB,CAAC,EAAE,CAAC,EAChC,GAAG,WAAW,EACf,GAAG,OAAO,IAAI,EAAE,CAAC;IAElB,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,QAAQ;QACnB,YAAY,EAAE,WAAW;QACzB,aAAa,EAAE,YAAY;QAC3B,KAAK;QACL,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,WAAW;KACf,CAAC;IAEF,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO,GAAG,qBAAqB,GAAG,WAAW,EAAE,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC9D,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC"}
|
||||
41
sdk/frontend/oauth2-login-sdk/package.json
Normal file
41
sdk/frontend/oauth2-login-sdk/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "oauth2-login-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript前端SDK,用于前后端分离项目对接统一登录系统",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && rollup -c",
|
||||
"dev": "tsc -w",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"oauth2",
|
||||
"login",
|
||||
"sdk",
|
||||
"typescript",
|
||||
"unified-login"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.56.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"rollup": "^4.9.6",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
2103
sdk/frontend/oauth2-login-sdk/pnpm-lock.yaml
generated
Normal file
2103
sdk/frontend/oauth2-login-sdk/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
sdk/frontend/oauth2-login-sdk/rollup.config.js
Normal file
23
sdk/frontend/oauth2-login-sdk/rollup.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
output: [
|
||||
{
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
{
|
||||
file: 'dist/index.esm.js',
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
clean: true
|
||||
})
|
||||
]
|
||||
};
|
||||
280
sdk/frontend/oauth2-login-sdk/src/core/auth.ts
Normal file
280
sdk/frontend/oauth2-login-sdk/src/core/auth.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
|
||||
import {EventType, RouterInfo, SDKConfig, UserInfo} from '../types';
|
||||
import {TokenManager} from './token';
|
||||
import {HttpClient} from './http';
|
||||
import {Storage} from '../utils/storage';
|
||||
import {buildQueryParams, isCallbackUrl, parseQueryParams} from '../utils/url';
|
||||
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export class Auth {
|
||||
private config: SDKConfig | null = null;
|
||||
private tokenManager: TokenManager;
|
||||
private httpClient: HttpClient;
|
||||
private storage: Storage;
|
||||
private eventHandlers: Record<EventType, Function[]> = {
|
||||
login: [],
|
||||
logout: [],
|
||||
tokenExpired: []
|
||||
};
|
||||
private userInfoCache: UserInfo | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage;
|
||||
// 先创建HttpClient,初始时tokenManager为undefined
|
||||
this.httpClient = new HttpClient(() => this.tokenManager.getToken() || null);
|
||||
// 然后创建TokenManager
|
||||
this.tokenManager = new TokenManager(storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: SDKConfig): void {
|
||||
this.config = config;
|
||||
// 设置租户ID到HTTP客户端
|
||||
this.httpClient.setTenantId(config.tenantId);
|
||||
}
|
||||
|
||||
getToken():string | null{
|
||||
return this.tokenManager.getToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
async login(redirectUri?: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp'
|
||||
const basepath = this.config.basepath || ''
|
||||
const path = `${basepath}/oauth2/authorization/${registrationId}`
|
||||
const tokenResponse = await this.httpClient.get(path,{needAuth:false})
|
||||
const redirect = tokenResponse.data.redirect_url
|
||||
const params = parseQueryParams(redirect)
|
||||
this.storage.set(params.state,window.location.href)
|
||||
window.location.href = redirect
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
async logout(): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
// 清除本地存储的Token和用户信息
|
||||
this.tokenManager.clearToken();
|
||||
this.userInfoCache = null;
|
||||
this.storage.remove('userInfo');
|
||||
const basepath = this.config.basepath || ''
|
||||
await this.httpClient.post(`${basepath}/logout`,null,{needAuth:true})
|
||||
// 触发退出事件
|
||||
this.emit('logout');
|
||||
window.location.href = this.config.idpLogoutUrl+'?redirect='+this.config.homePage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
async handleCallback(): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
|
||||
const params = parseQueryParams();
|
||||
|
||||
// 检查是否有错误
|
||||
if (params.error) {
|
||||
throw new Error(`Authorization error: ${params.error} - ${params.error_description || ''}`);
|
||||
}
|
||||
|
||||
// 检查是否有授权码
|
||||
if (!params.code) {
|
||||
throw new Error('Authorization code not found');
|
||||
}
|
||||
|
||||
const registrationId = this.config.registrationId || 'idp'
|
||||
const basepath = this.config.basepath || ''
|
||||
const callback = `${basepath}/login/oauth2/code/${registrationId}${buildQueryParams(params)}`
|
||||
const tokenResponse = await this.httpClient.get(callback,{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
needAuth: false
|
||||
})
|
||||
// 触发登录事件
|
||||
this.emit('login');
|
||||
this.storage.set('userInfo', tokenResponse.data.data);
|
||||
this.tokenManager.saveToken(tokenResponse.headers['authorization']||tokenResponse.headers['Authorization'])
|
||||
let url = this.config.homePage
|
||||
if(params.state){
|
||||
url = this.storage.get(params.state) || url;
|
||||
}
|
||||
window.location.href = url;
|
||||
|
||||
}
|
||||
|
||||
async getRoutes(): Promise<RouterInfo> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const basepath = this.config.basepath || ''
|
||||
const tokenResponse = await this.httpClient.get(`${basepath}/idp/routes`,{needAuth:true})
|
||||
if(tokenResponse.status===401){
|
||||
await this.logout()
|
||||
}
|
||||
return tokenResponse.data.data
|
||||
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
getUserInfo(): UserInfo {
|
||||
return this.storage.get("userInfo");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
async hasRole(role: string | string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo:UserInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles||[];
|
||||
|
||||
if (Array.isArray(role)) {
|
||||
// 检查是否有任一角色
|
||||
return role.some(r => roleCodes.includes(r));
|
||||
}
|
||||
|
||||
// 检查是否有单个角色
|
||||
return roleCodes.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
async hasAllRoles(roles: string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo:UserInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles||[];
|
||||
// 检查是否有所有角色
|
||||
return roles.every(r => roleCodes.includes(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
async hasPermission(permission: string | string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo:UserInfo = this.storage.get("userInfo");
|
||||
const permissions = userInfo.permissions||[];
|
||||
|
||||
if (Array.isArray(permission)) {
|
||||
// 检查是否有任一权限
|
||||
return permission.some(p => permissions.includes(p));
|
||||
}
|
||||
|
||||
// 检查是否有单个权限
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
async hasAllPermissions(permissions: string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo:UserInfo = this.storage.get("userInfo");
|
||||
const userPermissions = userInfo.permissions||[];
|
||||
|
||||
// 检查是否有所有权限
|
||||
return permissions.every(p => userPermissions.includes(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
// 检查Token是否存在且未过期
|
||||
return !!this.tokenManager.getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: EventType, callback: Function): void {
|
||||
this.eventHandlers[event].push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: EventType, callback: Function): void {
|
||||
this.eventHandlers[event] = this.eventHandlers[event].filter(handler => handler !== callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
private emit(event: EventType, data?: any): void {
|
||||
this.eventHandlers[event].forEach(handler => {
|
||||
try {
|
||||
handler(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in ${event} event handler:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean {
|
||||
return isCallbackUrl();
|
||||
}
|
||||
}
|
||||
370
sdk/frontend/oauth2-login-sdk/src/core/http.ts
Normal file
370
sdk/frontend/oauth2-login-sdk/src/core/http.ts
Normal file
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP请求方法类型
|
||||
*/
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
|
||||
/**
|
||||
* HTTP请求选项
|
||||
*/
|
||||
export interface HttpRequestOptions {
|
||||
/** 请求方法 */
|
||||
method: HttpMethod;
|
||||
/** 请求URL */
|
||||
url: string;
|
||||
/** 请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 请求体 */
|
||||
body?: any;
|
||||
/** 是否需要认证 */
|
||||
needAuth?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP响应类型
|
||||
*/
|
||||
export interface HttpResponse<T = any> {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 响应体 */
|
||||
data: T;
|
||||
/** 响应头 */
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
/** 状态码 */
|
||||
public status: number;
|
||||
/** 状态文本 */
|
||||
public statusText: string;
|
||||
/** 错误数据 */
|
||||
public data: any;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
*/
|
||||
constructor(message: string, status: number, statusText: string, data: any) {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export class HttpClient {
|
||||
private tokenGetter?: () => string | null;
|
||||
private tenantId?: string;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param logout
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
constructor(tokenGetter?: () => string | null) {
|
||||
this.tokenGetter = tokenGetter;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter: () => string | null): void {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId?: string): void {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>> {
|
||||
const {
|
||||
method,
|
||||
url,
|
||||
headers = {},
|
||||
body,
|
||||
needAuth = true
|
||||
} = options;
|
||||
|
||||
// 构建请求头
|
||||
const requestHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
};
|
||||
|
||||
// 添加认证头
|
||||
const addAuthHeader = () => {
|
||||
if (needAuth && this.tokenGetter) {
|
||||
const token = this.tokenGetter();
|
||||
if (token) {
|
||||
requestHeaders.Authorization = `${token}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 添加租户ID头
|
||||
if (this.tenantId) {
|
||||
requestHeaders['tenant-id'] = this.tenantId;
|
||||
}
|
||||
|
||||
addAuthHeader();
|
||||
|
||||
// 构建请求配置
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers: requestHeaders,
|
||||
credentials: 'include' // 包含cookie
|
||||
};
|
||||
|
||||
// 添加请求体
|
||||
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
}
|
||||
|
||||
try {
|
||||
// 发送请求
|
||||
const response = await fetch(url, fetchOptions);
|
||||
const responseData = await this.parseResponse(response);
|
||||
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
// 如果是401错误,尝试刷新Token并重试
|
||||
if (response.status === 401) {
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: '' as T,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
}
|
||||
}
|
||||
|
||||
// 其他错误,直接抛出
|
||||
const errorMsg = this.getErrorMessage(responseData);
|
||||
throw new HttpError(
|
||||
errorMsg,
|
||||
response.status,
|
||||
response.statusText,
|
||||
responseData
|
||||
);
|
||||
}
|
||||
|
||||
// 处理成功响应的业务逻辑
|
||||
return this.handleResponse(response, responseData);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 网络错误或其他错误
|
||||
throw new HttpError(
|
||||
error instanceof Error ? error.message : 'Network Error',
|
||||
0,
|
||||
'Network Error',
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 处理响应数据
|
||||
* @param response 响应对象
|
||||
* @param responseData 响应数据
|
||||
* @returns HttpResponse<T> 处理后的响应
|
||||
*/
|
||||
private handleResponse<T = any>(response: Response, responseData: any): HttpResponse<T> {
|
||||
// 检查是否为业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
// 业务响应结构:{ code, msg, data }
|
||||
const { code, msg, data } = responseData;
|
||||
|
||||
// 检查业务状态码
|
||||
if (code !== 0 && code !== 200 && code !== '0' && code !== '200') {
|
||||
// 业务错误,抛出HttpError
|
||||
throw new HttpError(
|
||||
msg || `Business Error: ${code}`,
|
||||
response.status,
|
||||
response.statusText,
|
||||
responseData
|
||||
);
|
||||
}
|
||||
|
||||
// 业务成功,返回data字段作为实际数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: data as T,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
|
||||
// 非业务响应结构,直接返回原始数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: responseData as T,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param responseData 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
private isBusinessResponse(responseData: any): boolean {
|
||||
return typeof responseData === 'object' &&
|
||||
responseData !== null &&
|
||||
('code' in responseData) &&
|
||||
('msg' in responseData) &&
|
||||
('data' in responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @param responseData 响应数据
|
||||
* @returns string 错误信息
|
||||
*/
|
||||
private getErrorMessage(responseData: any): string {
|
||||
// 如果是业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
return responseData.msg || `Business Error: ${responseData.code}`;
|
||||
}
|
||||
|
||||
// 其他错误结构
|
||||
return responseData.message || responseData.error || `HTTP Error`;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async get<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'GET',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async post<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async put<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'PUT',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async delete<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async patch<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'PATCH',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
private async parseResponse(response: Response): Promise<any> {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
} else if (contentType.includes('text/')) {
|
||||
return response.text();
|
||||
} else {
|
||||
return response.blob();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
private parseHeaders(headers: Headers): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
headers.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
45
sdk/frontend/oauth2-login-sdk/src/core/token.ts
Normal file
45
sdk/frontend/oauth2-login-sdk/src/core/token.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
/**
|
||||
* Token管理类
|
||||
*/
|
||||
export class TokenManager {
|
||||
private storage: Storage;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param httpClient HTTP客户端实例
|
||||
*/
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
saveToken(tokenInfo: string): void {
|
||||
this.storage.set('token', tokenInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns TokenInfo | null Token信息
|
||||
*/
|
||||
getToken(): string | null {
|
||||
return this.storage.get('token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken(): void {
|
||||
this.storage.remove('token');
|
||||
}
|
||||
}
|
||||
130
sdk/frontend/oauth2-login-sdk/src/guards/router.ts
Normal file
130
sdk/frontend/oauth2-login-sdk/src/guards/router.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
|
||||
import { Auth } from '../core/auth';
|
||||
|
||||
/**
|
||||
* 路由守卫选项
|
||||
*/
|
||||
export interface RouterGuardOptions {
|
||||
/**
|
||||
* 是否需要登录
|
||||
*/
|
||||
requiresAuth?: boolean;
|
||||
/**
|
||||
* 需要的权限列表
|
||||
*/
|
||||
requiredPermissions?: string[];
|
||||
/**
|
||||
* 登录后重定向的URL
|
||||
*/
|
||||
redirectUri?: string;
|
||||
/**
|
||||
* 权限不足时重定向的URL
|
||||
*/
|
||||
unauthorizedRedirectUri?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export class RouterGuard {
|
||||
private auth: Auth;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth: Auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<boolean> 是否通过权限检查
|
||||
*/
|
||||
async check(options: RouterGuardOptions): Promise<boolean> {
|
||||
const { requiresAuth = true, requiredPermissions = [] } = options;
|
||||
|
||||
// 检查是否需要登录
|
||||
if (requiresAuth) {
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
// 未认证,跳转到登录页
|
||||
this.auth.login(options.redirectUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否需要权限
|
||||
if (requiredPermissions.length > 0) {
|
||||
// 获取用户权限
|
||||
const userPermissions = [''];
|
||||
|
||||
// 检查是否拥有所有需要的权限
|
||||
const hasPermission = requiredPermissions.every(permission =>
|
||||
userPermissions.includes(permission)
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
// 权限不足,跳转到权限不足页
|
||||
if (options.unauthorizedRedirectUri) {
|
||||
window.location.href = options.unauthorizedRedirectUri;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns 路由守卫函数
|
||||
*/
|
||||
createVueGuard() {
|
||||
return async (to: any, from: any, next: any) => {
|
||||
// 从路由元信息中获取守卫选项
|
||||
const options: RouterGuardOptions = to.meta?.auth || {};
|
||||
|
||||
try {
|
||||
const allowed = await this.check(options);
|
||||
if (allowed) {
|
||||
next();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
async hasPermission(permissions: string | string[]): Promise<boolean> {
|
||||
if (!permissions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
|
||||
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取用户权限
|
||||
const userPermissions = ['']
|
||||
|
||||
// 检查是否拥有所有需要的权限
|
||||
return requiredPermissions.every(permission =>
|
||||
userPermissions.includes(permission)
|
||||
);
|
||||
}
|
||||
}
|
||||
94
sdk/frontend/oauth2-login-sdk/src/index.ts
Normal file
94
sdk/frontend/oauth2-login-sdk/src/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 统一登录SDK入口文件
|
||||
* 支持OAuth2授权码模式,提供完整的Token管理和用户信息管理功能
|
||||
*/
|
||||
|
||||
// 导出核心类和功能
|
||||
export { Auth } from './core/auth';
|
||||
export { TokenManager } from './core/token';
|
||||
export { HttpClient, HttpError } from './core/http';
|
||||
export { Storage } from './utils/storage';
|
||||
export { RouterGuard, RouterGuardOptions } from './guards/router';
|
||||
|
||||
// 导出工具函数
|
||||
export {
|
||||
generateRandomString,
|
||||
parseQueryParams,
|
||||
buildQueryParams,
|
||||
generateAuthorizationUrl,
|
||||
isCallbackUrl
|
||||
} from './utils/url';
|
||||
|
||||
// 导出类型定义
|
||||
export * from './types';
|
||||
|
||||
// 导出Vue插件
|
||||
export { VuePlugin, createVuePlugin } from './plugins/vue';
|
||||
|
||||
// 创建默认SDK实例
|
||||
import { SDKConfig, UnifiedLoginSDK } from './types';
|
||||
import { Auth as AuthCore } from './core/auth';
|
||||
import { Storage as StorageCore } from './utils/storage';
|
||||
|
||||
/**
|
||||
* 默认SDK实例
|
||||
*/
|
||||
const defaultStorage = new StorageCore();
|
||||
const defaultAuth = new AuthCore(defaultStorage);
|
||||
|
||||
/**
|
||||
* 默认导出的SDK实例
|
||||
*/
|
||||
export const unifiedLoginSDK: UnifiedLoginSDK = {
|
||||
init: (config: SDKConfig) => {
|
||||
defaultAuth.init(config);
|
||||
},
|
||||
getToken: () => {
|
||||
return defaultAuth.getToken()
|
||||
},
|
||||
login: (redirectUri?: string) => {
|
||||
return defaultAuth.login(redirectUri);
|
||||
},
|
||||
logout: () => {
|
||||
return defaultAuth.logout();
|
||||
},
|
||||
handleCallback: () => {
|
||||
return defaultAuth.handleCallback();
|
||||
},
|
||||
getRoutes: () => {
|
||||
return defaultAuth.getRoutes();
|
||||
},
|
||||
getUserInfo: () => {
|
||||
return defaultAuth.getUserInfo();
|
||||
},
|
||||
isAuthenticated: () => {
|
||||
return defaultAuth.isAuthenticated();
|
||||
},
|
||||
hasRole: (role: string | string[]) => {
|
||||
return defaultAuth.hasRole(role);
|
||||
},
|
||||
hasAllRoles: (roles: string[]) => {
|
||||
return defaultAuth.hasAllRoles(roles);
|
||||
},
|
||||
hasPermission: (permission: string | string[]) => {
|
||||
return defaultAuth.hasPermission(permission);
|
||||
},
|
||||
hasAllPermissions: (permissions: string[]) => {
|
||||
return defaultAuth.hasAllPermissions(permissions);
|
||||
},
|
||||
on: (event, callback) => {
|
||||
return defaultAuth.on(event, callback);
|
||||
},
|
||||
off: (event, callback) => {
|
||||
return defaultAuth.off(event, callback);
|
||||
},
|
||||
isCallback: () => {
|
||||
return defaultAuth.isCallback();
|
||||
}
|
||||
};
|
||||
|
||||
// 默认导出
|
||||
export default unifiedLoginSDK;
|
||||
|
||||
// 版本信息
|
||||
export const version = '1.0.0';
|
||||
121
sdk/frontend/oauth2-login-sdk/src/plugins/vue.ts
Normal file
121
sdk/frontend/oauth2-login-sdk/src/plugins/vue.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
|
||||
import { Auth } from '../core/auth';
|
||||
import { SDKConfig } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
|
||||
/**
|
||||
* Vue插件选项
|
||||
*/
|
||||
export interface VuePluginOptions {
|
||||
/**
|
||||
* SDK配置
|
||||
*/
|
||||
config: SDKConfig;
|
||||
/**
|
||||
* 插件名称,默认'unifiedLogin'
|
||||
*/
|
||||
pluginName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export class VuePlugin {
|
||||
private auth: Auth;
|
||||
private routerGuard: RouterGuard;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage) {
|
||||
this.auth = new Auth(storage);
|
||||
this.routerGuard = new RouterGuard(this.auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app: any, options: VuePluginOptions): void {
|
||||
const { config, pluginName = 'unifiedLogin' } = options;
|
||||
|
||||
// 初始化SDK
|
||||
this.auth.init(config);
|
||||
|
||||
// 判断是Vue 2还是Vue 3
|
||||
const isVue3 = typeof app.config !== 'undefined';
|
||||
|
||||
if (isVue3) {
|
||||
// Vue 3
|
||||
// 在全局属性上挂载SDK实例
|
||||
app.config.globalProperties[`${pluginName}`] = this.auth;
|
||||
app.config.globalProperties.$auth = this.auth; // 兼容简写
|
||||
|
||||
// 提供Vue组件内的注入
|
||||
app.provide(pluginName, this.auth);
|
||||
app.provide('auth', this.auth); // 兼容简写
|
||||
|
||||
// 处理路由守卫
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Vue 2
|
||||
// 在Vue实例上挂载SDK实例
|
||||
app.prototype[`${pluginName}`] = this.auth;
|
||||
app.prototype.$auth = this.auth; // 兼容简写
|
||||
|
||||
// 全局混入
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth(): Auth {
|
||||
return this.auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard(): RouterGuard {
|
||||
return this.routerGuard;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export function createVuePlugin(storageType?: 'localStorage' | 'sessionStorage' | 'cookie'): VuePlugin {
|
||||
const storage = new Storage(storageType);
|
||||
return new VuePlugin(storage);
|
||||
}
|
||||
40
sdk/frontend/oauth2-login-sdk/src/types/config.ts
Normal file
40
sdk/frontend/oauth2-login-sdk/src/types/config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* SDK配置选项
|
||||
*/
|
||||
export interface SDKConfig {
|
||||
/** 客户端ID */
|
||||
clientId: string;
|
||||
/** 注册id **/
|
||||
registrationId: string,
|
||||
/** 后端basepath路径*/
|
||||
basepath: string,
|
||||
/** 存储类型,默认localStorage */
|
||||
storageType?: 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
idpLogoutUrl:string;
|
||||
homePage: string;
|
||||
/** 租户ID(可选) */
|
||||
tenantId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token信息
|
||||
*/
|
||||
export interface TokenInfo {
|
||||
/** 访问令牌 */
|
||||
accessToken: string;
|
||||
/** 刷新令牌 */
|
||||
refreshToken: string;
|
||||
/** 令牌类型,默认Bearer */
|
||||
tokenType?: string;
|
||||
/** 访问令牌过期时间(秒) */
|
||||
expiresIn: number;
|
||||
/** 刷新令牌过期时间(秒) */
|
||||
refreshExpiresIn?: number;
|
||||
/** 令牌颁发时间戳 */
|
||||
issuedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
export type EventType = 'login' | 'logout' | 'tokenExpired';
|
||||
94
sdk/frontend/oauth2-login-sdk/src/types/index.ts
Normal file
94
sdk/frontend/oauth2-login-sdk/src/types/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {RouterInfo} from "./user";
|
||||
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
|
||||
/**
|
||||
* 统一登录SDK接口
|
||||
*/
|
||||
export interface UnifiedLoginSDK {
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: import('./config').SDKConfig): void;
|
||||
|
||||
getToken():string|null
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<import('./user').RouterInfo>;
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
getUserInfo(): import('./user').UserInfo;
|
||||
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: import('./config').EventType, callback: Function): void;
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: import('./config').EventType, callback: Function): void;
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
88
sdk/frontend/oauth2-login-sdk/src/types/user.ts
Normal file
88
sdk/frontend/oauth2-login-sdk/src/types/user.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 菜单信息
|
||||
*/
|
||||
export interface RouterInfo {
|
||||
/** 菜单名称 */
|
||||
name: string;
|
||||
/** 菜单路径 */
|
||||
path?: string;
|
||||
hidden: boolean;
|
||||
redirect: string;
|
||||
query: string;
|
||||
alwaysShow: boolean;
|
||||
/** 菜单组件 */
|
||||
component?: string;
|
||||
meta:MetaVo;
|
||||
children: RouterInfo;
|
||||
}
|
||||
export interface MetaVo {
|
||||
/**
|
||||
* 设置该路由在侧边栏和面包屑中展示的名字
|
||||
*/
|
||||
title:string;
|
||||
|
||||
/**
|
||||
* 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
*/
|
||||
icon:string;
|
||||
|
||||
/**
|
||||
* 设置为true,则不会被 <keep-alive>缓存
|
||||
*/
|
||||
noCache:boolean;
|
||||
|
||||
/**
|
||||
* 内链地址(http(s)://开头)
|
||||
*/
|
||||
link:string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户基本信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 姓名 */
|
||||
nickName: string;
|
||||
/** 邮箱 */
|
||||
currentDeptId: string;
|
||||
/** 部门 */
|
||||
userDepts?: UserDept[];
|
||||
/** 岗位 */
|
||||
userPost?: UserPost[];
|
||||
/** 性别 */
|
||||
sex: string;
|
||||
/** 用户角色 */
|
||||
roles?: string[];
|
||||
/** 权限列表 */
|
||||
permissions?: string[];
|
||||
dataPermission: DataPermission
|
||||
}
|
||||
|
||||
export interface DataPermission {
|
||||
allowAll: boolean;
|
||||
onlySelf: boolean;
|
||||
deptList?: string[];
|
||||
areas?:string[]
|
||||
}
|
||||
export interface UserDept{
|
||||
postCode:string
|
||||
postId:bigint
|
||||
postName:string
|
||||
postSort:bigint
|
||||
remark:string
|
||||
status:bigint
|
||||
}
|
||||
export interface UserPost{
|
||||
ancestors: string
|
||||
deptId: bigint
|
||||
deptName: string
|
||||
leader: string
|
||||
orderNum: bigint
|
||||
parentId: bigint
|
||||
status: bigint
|
||||
}
|
||||
358
sdk/frontend/oauth2-login-sdk/src/utils/storage.ts
Normal file
358
sdk/frontend/oauth2-login-sdk/src/utils/storage.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
|
||||
type StorageType = 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export class Storage {
|
||||
private storageType: StorageType;
|
||||
private prefix: string;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType: StorageType = 'localStorage', prefix: string = 'unified_login_') {
|
||||
this.storageType = storageType;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key: string, value: any, options?: { expires?: number; path?: string; domain?: string; secure?: boolean }): void {
|
||||
const fullKey = this.prefix + key;
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.setLocalStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.setSessionStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.setCookie(fullKey, stringValue, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key: string): any {
|
||||
const fullKey = this.prefix + key;
|
||||
let value: any;
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
value = this.getLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
value = this.getSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
value = this.getCookie(fullKey);
|
||||
break;
|
||||
default:
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
// 如果不是JSON,直接返回字符串
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key: string): void {
|
||||
const fullKey = this.prefix + key;
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.removeLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.removeSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.removeCookie(fullKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear(): void {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.clearLocalStorage();
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.clearSessionStorage();
|
||||
break;
|
||||
case 'cookie':
|
||||
this.clearCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable(): boolean {
|
||||
try {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
return this.isLocalStorageAvailable();
|
||||
case 'sessionStorage':
|
||||
return this.isSessionStorageAvailable();
|
||||
case 'cookie':
|
||||
return typeof document !== 'undefined';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ localStorage 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
private setLocalStorage(key: string, value: string): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
private getLocalStorage(key: string): string | null {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
private removeLocalStorage(key: string): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
private clearLocalStorage(): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
localStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
private isLocalStorageAvailable(): boolean {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
localStorage.setItem(testKey, testKey);
|
||||
localStorage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ sessionStorage 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
private setSessionStorage(key: string, value: string): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
private getSessionStorage(key: string): string | null {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
private removeSessionStorage(key: string): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
private clearSessionStorage(): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
for (let i = 0; i < sessionStorage.length; i++) {
|
||||
const key = sessionStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
sessionStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
private isSessionStorageAvailable(): boolean {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
sessionStorage.setItem(testKey, testKey);
|
||||
sessionStorage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ cookie 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
private setCookie(
|
||||
key: string,
|
||||
value: string,
|
||||
options?: { expires?: number; path?: string; domain?: string; secure?: boolean }
|
||||
): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
let cookieString = `${key}=${encodeURIComponent(value)}`;
|
||||
|
||||
if (options) {
|
||||
// 设置过期时间(秒)
|
||||
if (options.expires) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + options.expires * 1000);
|
||||
cookieString += `; expires=${date.toUTCString()}`;
|
||||
}
|
||||
|
||||
// 设置路径
|
||||
if (options.path) {
|
||||
cookieString += `; path=${options.path}`;
|
||||
}
|
||||
|
||||
// 设置域名
|
||||
if (options.domain) {
|
||||
cookieString += `; domain=${options.domain}`;
|
||||
}
|
||||
|
||||
// 设置secure
|
||||
if (options.secure) {
|
||||
cookieString += '; secure';
|
||||
}
|
||||
}
|
||||
|
||||
document.cookie = cookieString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
private getCookie(key: string): string | null {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = `${key}=`;
|
||||
const decodedCookie = decodeURIComponent(document.cookie);
|
||||
const ca = decodedCookie.split(';');
|
||||
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
private removeCookie(key: string): void {
|
||||
this.setCookie(key, '', { expires: -1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
private clearCookie(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const eqPos = cookie.indexOf('=');
|
||||
const key = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (key.startsWith(this.prefix)) {
|
||||
this.removeCookie(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
sdk/frontend/oauth2-login-sdk/src/utils/url.ts
Normal file
125
sdk/frontend/oauth2-login-sdk/src/utils/url.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export function generateRandomString(length: number = 32): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export function parseQueryParams(url: string = window.location.href): Record<string, string> {
|
||||
const params: Record<string, string> = {};
|
||||
const queryString = url.split('?')[1];
|
||||
if (!queryString) {
|
||||
return params;
|
||||
}
|
||||
|
||||
const pairs = queryString.split('&');
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=');
|
||||
if (key) {
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export function buildQueryParams(params: Record<string, any>): string {
|
||||
const pairs: string[] = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return pairs.length ? `?${pairs.join('&')}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export function generateAuthorizationUrl(
|
||||
authorizationEndpoint: string,
|
||||
clientId: string,
|
||||
redirectUri: string,
|
||||
options?: {
|
||||
responseType?: string;
|
||||
scope?: string;
|
||||
state?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
): string {
|
||||
const {
|
||||
responseType = 'code',
|
||||
scope,
|
||||
state = generateRandomString(32),
|
||||
...extraParams
|
||||
} = options || {};
|
||||
|
||||
const params = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: responseType,
|
||||
state,
|
||||
...(scope ? { scope } : {}),
|
||||
...extraParams
|
||||
};
|
||||
|
||||
const queryString = buildQueryParams(params);
|
||||
return `${authorizationEndpoint}${queryString}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export function isCallbackUrl(url: string = window.location.href): boolean {
|
||||
const params = parseQueryParams(url);
|
||||
return !!params.code || !!params.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export function getPathname(url: string = window.location.href): string {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export function getHostname(url: string = window.location.href): string {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname;
|
||||
}
|
||||
27
sdk/frontend/oauth2-login-sdk/tsconfig.json
Normal file
27
sdk/frontend/oauth2-login-sdk/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["ES2018", "DOM"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user