Authorization
Authorization
為什么要有auth code
http://www.rzrgm.cn/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系統。

浙公網安備 33010602011771號