@55387.ai/uniauth-server 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,116 @@ app.get('/api/profile', (c) => {
|
|
|
63
63
|
});
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## SSO OAuth2 后端代理登录
|
|
67
|
+
|
|
68
|
+
当应用配置为 **Confidential Client**(机密客户端)时,需要通过后端完成 Token 交换。
|
|
69
|
+
|
|
70
|
+
### 流程概述
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
用户 → 前端 → /api/auth/login → 后端生成授权 URL → 重定向到 SSO
|
|
74
|
+
↓
|
|
75
|
+
用户 ← 前端 ← / ← 后端设置 Cookie ← SSO 回调到 /api/auth/callback
|
|
76
|
+
↑
|
|
77
|
+
后端用 client_secret 交换 Token
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### API 端点
|
|
81
|
+
|
|
82
|
+
| 端点 | URL |
|
|
83
|
+
|------|-----|
|
|
84
|
+
| 授权端点 | `https://sso.55387.xyz/api/v1/oauth2/authorize` |
|
|
85
|
+
| Token 端点 | `https://sso.55387.xyz/api/v1/oauth2/token` |
|
|
86
|
+
| 用户信息端点 | `https://sso.55387.xyz/api/v1/oauth2/userinfo` |
|
|
87
|
+
|
|
88
|
+
### 实现示例(Hono)
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { Hono } from 'hono';
|
|
92
|
+
import { setCookie, getCookie } from 'hono/cookie';
|
|
93
|
+
|
|
94
|
+
const app = new Hono();
|
|
95
|
+
|
|
96
|
+
// 登录端点 - 重定向到 SSO
|
|
97
|
+
app.get('/api/auth/login', (c) => {
|
|
98
|
+
const origin = c.req.header('origin') || 'http://localhost:3000';
|
|
99
|
+
const redirectUri = `${origin}/api/auth/callback`;
|
|
100
|
+
|
|
101
|
+
const params = new URLSearchParams({
|
|
102
|
+
client_id: process.env.UNIAUTH_CLIENT_ID,
|
|
103
|
+
redirect_uri: redirectUri,
|
|
104
|
+
response_type: 'code',
|
|
105
|
+
scope: 'openid profile email phone',
|
|
106
|
+
state: generateRandomState(), // 生成随机 state 防止 CSRF
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return c.redirect(`https://sso.55387.xyz/api/v1/oauth2/authorize?${params}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 回调端点 - 交换 Token
|
|
113
|
+
app.get('/api/auth/callback', async (c) => {
|
|
114
|
+
const code = c.req.query('code');
|
|
115
|
+
const origin = c.req.header('referer')?.replace(/\/api\/auth\/callback.*$/, '') || 'http://localhost:3000';
|
|
116
|
+
|
|
117
|
+
// 用授权码交换 Token
|
|
118
|
+
const response = await fetch('https://sso.55387.xyz/api/v1/oauth2/token', {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'Content-Type': 'application/json' },
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
client_id: process.env.UNIAUTH_CLIENT_ID,
|
|
123
|
+
client_secret: process.env.UNIAUTH_CLIENT_SECRET,
|
|
124
|
+
code,
|
|
125
|
+
grant_type: 'authorization_code',
|
|
126
|
+
redirect_uri: `${origin}/api/auth/callback`,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const { access_token, id_token } = await response.json();
|
|
131
|
+
|
|
132
|
+
// 将 Token 存储到 httpOnly Cookie
|
|
133
|
+
setCookie(c, 'auth_token', id_token, {
|
|
134
|
+
httpOnly: true,
|
|
135
|
+
secure: true,
|
|
136
|
+
sameSite: 'Lax',
|
|
137
|
+
maxAge: 60 * 60 * 24 * 7, // 7 天
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return c.redirect('/');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 检查登录状态
|
|
144
|
+
app.get('/api/auth/status', async (c) => {
|
|
145
|
+
const token = getCookie(c, 'auth_token');
|
|
146
|
+
if (!token) {
|
|
147
|
+
return c.json({ authenticated: false });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 验证 Token
|
|
151
|
+
try {
|
|
152
|
+
const payload = await auth.verifyToken(token);
|
|
153
|
+
return c.json({ authenticated: true, userId: payload.sub });
|
|
154
|
+
} catch {
|
|
155
|
+
return c.json({ authenticated: false });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 前端调用
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// 触发登录
|
|
164
|
+
const handleLogin = () => {
|
|
165
|
+
window.location.href = '/api/auth/login';
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// 检查登录状态
|
|
169
|
+
const checkAuth = async () => {
|
|
170
|
+
const response = await fetch('/api/auth/status', { credentials: 'include' });
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
return data.authenticated;
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
66
176
|
## OAuth2 Token Introspection (RFC 7662)
|
|
67
177
|
|
|
68
178
|
```typescript
|