Authorization
Authorization
为什么要有auth code
https://www.cnblogs.com/blowing00/p/4524412.html
四大方式
https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
对于第一种“第一种授权方式:授权码”, 作者说token存储在后台,这样话用户的会话控制怎么做到的?我理解token应该是浏览器发送请求时候带着的,然后后端拿到token做合法性校验。
你的理解是正确的!这里确实容易产生混淆。让我详细解释一下**授权码模式**中token的实际处理流程:
## 授权码模式的完整流程
### 1. 授权码交换(后端操作)
```
后端服务器 -> 授权服务器:
POST /token
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET"
}
授权服务器 -> 后端服务器:
{
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN",
"expires_in": 3600
}
```
### 2. Token的处理方式(有多种选择)
#### 方式一:Token存储在后端 + Session管理
```javascript
// 后端获取token后
app.post('/callback', async (req, res) => {
const { code } = req.body;
// 1. 用授权码换取access_token(后端操作)
const tokenResponse = await exchangeCodeForToken(code);
const { access_token, refresh_token } = tokenResponse;
// 2. 将token存储在后端(如Redis、数据库)
const sessionId = generateSessionId();
await redis.set(`session:${sessionId}`, JSON.stringify({
access_token,
refresh_token,
user_id: userId
}));
// 3. 返回session_id给前端
res.cookie('session_id', sessionId, { httpOnly: true });
res.json({ success: true });
});
// 后续API请求
app.get('/api/user-data', async (req, res) => {
const sessionId = req.cookies.session_id;
const session = await redis.get(`session:${sessionId}`);
const { access_token } = JSON.parse(session);
// 用access_token调用第三方API
const userData = await fetch('https://api.github.com/user', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
res.json(userData);
});
```
#### 方式二:Token返回给前端(更常见)
```javascript
// 后端获取token后直接返回给前端
app.post('/callback', async (req, res) => {
const { code } = req.body;
// 1. 用授权码换取access_token
const tokenResponse = await exchangeCodeForToken(code);
// 2. 直接返回token给前端
res.json({
access_token: tokenResponse.access_token,
expires_in: tokenResponse.expires_in
});
});
```
```javascript
// 前端存储token并使用
localStorage.setItem('access_token', response.access_token);
// 前端发送API请求时携带token
fetch('/api/data', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
```
#### 方式三:后端生成自己的JWT Token
```javascript
app.post('/callback', async (req, res) => {
const { code } = req.body;
// 1. 换取OAuth access_token
const { access_token } = await exchangeCodeForToken(code);
// 2. 获取用户信息
const userInfo = await fetchUserInfo(access_token);
// 3. 生成自己的JWT token
const jwtToken = jwt.sign(
{ userId: userInfo.id, provider_token: access_token },
JWT_SECRET,
{ expiresIn: '1h' }
);
// 4. 返回JWT给前端
res.json({ token: jwtToken });
});
```
## 为什么说"token存储在后台更安全"?
### 安全优势:
1. **避免XSS攻击**:token不会暴露在前端JavaScript环境中
2. **避免CSRF攻击**:使用httpOnly cookie
3. **集中管理**:可以随时撤销session
4. **防止token泄露**:token不会出现在浏览器开发者工具中
### 实际应用中的选择:
**企业级应用**通常选择方式一(后端存储):
```
用户 -> 前端 -> 后端 -> 第三方API
(session) (access_token)
```
**轻量级应用**通常选择方式二(前端存储):
```
用户 -> 前端 -> 第三方API
(access_token)
```
## 总结
你的理解完全正确!**授权码模式的核心安全点**是:
- ✅ 授权码交换access_token的过程在后端进行(避免client_secret泄露)
- ✅ 但获得token后的使用方式可以灵活选择
- ✅ 无论哪种方式,最终都需要在HTTP请求中携带某种凭证进行身份验证
作者提到的"token存储在后台"只是一种可选的安全实践,而不是授权码模式的必须要求。
你给例子代码,都是使用access_token向authorization服务器访问资源的,access_token可以用于保护本服务器的api吗?
非常好的问题!你指出了一个重要的概念区别。让我详细解释不同的场景:
## OAuth Access Token的原始用途
OAuth access_token的**标准用途**是访问**资源服务器**的受保护资源:
```javascript
// 标准OAuth流程:用access_token访问第三方资源服务器
fetch('https://api.github.com/user', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
```
## 保护自己API的几种方式
### 方式一:直接使用OAuth Access Token(如果你就是资源服务器)
如果你的服务器**同时是授权服务器和资源服务器**:
```javascript
// 你的API端点验证OAuth access_token
app.get('/api/user-profile', async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
// 验证access_token的有效性
const isValid = await validateOAuthToken(token);
if (!isValid) {
return res.status(401).json({ error: 'Invalid token' });
}
// 获取token关联的用户信息
const userInfo = await getUserFromToken(token);
res.json({ profile: userInfo });
});
async function validateOAuthToken(token) {
// 检查token是否在你的数据库中且未过期
const tokenRecord = await db.query(
'SELECT * FROM oauth_tokens WHERE access_token = ? AND expires_at > NOW()',
[token]
);
return tokenRecord.length > 0;
}
```
### 方式二:转换为自己的Token系统(更常见)
大多数应用会将OAuth token转换为自己的认证token:
```javascript
// OAuth回调:将第三方access_token转换为自己的JWT
app.post('/auth/callback', async (req, res) => {
const { code } = req.body;
// 1. 用授权码换取第三方access_token
const oauthResponse = await exchangeCodeForToken(code);
const { access_token: githubToken } = oauthResponse;
// 2. 用第三方token获取用户信息
const githubUser = await fetch('https://api.github.com/user', {
headers: { 'Authorization': `Bearer ${githubToken}` }
}).then(r => r.json());
// 3. 在本地创建或更新用户
const localUser = await createOrUpdateUser({
github_id: githubUser.id,
username: githubUser.login,
email: githubUser.email
});
// 4. 生成自己的JWT token
const myJwtToken = jwt.sign(
{
userId: localUser.id,
username: localUser.username,
// 可选:保存第三方token用于后续API调用
github_token: githubToken
},
JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token: myJwtToken });
});
// 保护自己的API端点
app.get('/api/my-data', authenticateJWT, (req, res) => {
// req.user 来自JWT验证中间件
res.json({
message: `Hello ${req.user.username}`,
userData: getUserData(req.user.userId)
});
});
function authenticateJWT(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
```
### 方式三:Session + OAuth Token
```javascript
// 使用session存储OAuth信息
app.post('/auth/callback', async (req, res) => {
const { code } = req.body;
const oauthResponse = await exchangeCodeForToken(code);
const userInfo = await fetchUserInfo(oauthResponse.access_token);
// 存储到session
req.session.user = {
id: userInfo.id,
username: userInfo.login,
oauth_token: oauthResponse.access_token
};
res.json({ success: true });
});
// 保护API
app.get('/api/protected', requireAuth, (req, res) => {
res.json({
message: `Welcome ${req.session.user.username}`,
data: getProtectedData(req.session.user.id)
});
});
function requireAuth(req, res, next) {
if (!req.session.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
next();
}
```
### 方式四:混合使用(调用第三方API时使用OAuth Token)
```javascript
// 既保护自己的API,又能调用第三方API
app.get('/api/github-repos', authenticateJWT, async (req, res) => {
// 用自己的JWT验证用户身份
const userId = req.user.userId;
// 获取存储的GitHub token
const userTokens = await db.query(
'SELECT github_token FROM user_tokens WHERE user_id = ?',
[userId]
);
if (!userTokens.github_token) {
return res.status(400).json({ error: 'GitHub not connected' });
}
// 用GitHub token调用GitHub API
const repos = await fetch('https://api.github.com/user/repos', {
headers: { 'Authorization': `Bearer ${userTokens.github_token}` }
}).then(r => r.json());
res.json({ repos });
});
```
## 总结
**OAuth access_token的使用场景**:
1. **访问第三方资源**:✅ 这是标准用途
```javascript
// 用GitHub的access_token访问GitHub API
fetch('https://api.github.com/user', {
headers: { 'Authorization': `Bearer ${github_access_token}` }
});
```
2. **保护自己的API**:❓ 需要看具体架构
- 如果你是OAuth提供商:可以直接使用
- 如果你是OAuth客户端:通常转换为自己的token系统
3. **最佳实践**:
```javascript
// 前端:使用你自己的token访问你的API
fetch('/api/my-data', {
headers: { 'Authorization': `Bearer ${my_jwt_token}` }
});
// 后端:需要时用存储的OAuth token访问第三方API
fetch('https://api.github.com/user/repos', {
headers: { 'Authorization': `Bearer ${stored_github_token}` }
});
```
所以答案是:OAuth access_token**可以**用于保护自己的API,但在实际应用中,**更常见的做法**是将其转换为自己的认证token系统。