OAuth 2.0: код авторизации, потоки и токены доступа
Это объяснение ключевых понятий OAuth 2.0 Framework с разбором потоков, токенов и практической реализацией.
Суть
OAuth 2.0 - это стандартизированный фреймворк авторизации, позволяющий третьим сторонам получать доступ к ресурсам пользователя без знания его учетных данных.
Определение
OAuth 2.0 - широко используемый протокол авторизации для веб-, десктопных и мобильных приложений. Он дает возможность приложению (клиенту) получать доступ от имени пользователя к ресурсам другой системы (сервера ресурсов).
Основные характеристики:
- Делегирование: пользователи передают права доступа приложениям
- Токены вместо паролей: использование Access Token вместо пароля
- Четкая ролевая модель: разделение ответственности между участниками
- Стандартизация: RFC 6749 определяет фреймворк
Участники процесса:
- Resource Owner: пользователь, которому принадлежат ресурсы
- Client: приложение, которое хочет получить доступ к ресурсам
- Authorization Server: выдает и валидирует токены
- Resource Server: предоставляет защищенные ресурсы
OAuth 2.0 не передает личность пользователя, только авторизует доступ к ресурсам. Для проверки личности используется OpenID Connect.
Ключевые моменты для экзаменов
- Фреймворк авторизации, не аутентификации
- Роли: Resource Owner, Client, Authorization Server, Resource Server
- Access Token и опционально Refresh Token
- Authorization Code Flow для веб-приложений
- Client Credentials Flow для общения сервер-сервер
- HTTP-протокол, поддержка REST API
- Токены повышают безопасность
- Защита от передачи пароля через механизм делегирования
Основные компоненты
- Resource Owner: пользователь или система, владеющие ресурсами
- Client: приложение, которому нужен доступ к ресурсам
- Authorization Server: выдает и проверяет токены
- Resource Server: хостит защищенные ресурсы
- Access Token: краткосрочный токен для доступа к ресурсам
- Refresh Token: долгосрочный токен для получения новых Access Token
- Authorization Code: временный код для обмена на токен
- Redirect URI: URL перенаправления после авторизации
- Scope: определяет область доступа
- Grant Type: тип потока OAuth 2.0
Потоки OAuth 2.0
1. Authorization Code Flow (для веб-приложений)
1. Client → User: переадресация на Authorization Server
2. User → Authorization Server: вход и подтверждение
3. Authorization Server → Client: Authorization Code
4. Client → Authorization Server: обмен Code на Access Token
5. Authorization Server → Client: Access Token + Refresh Token
6. Client → Resource Server: запрос ресурсов с Access Token
2. Client Credentials Flow (для общения сервер-сервер)
1. Client → Authorization Server: отправка учетных данных
2. Authorization Server → Client: Access Token
3. Client → Resource Server: запрос ресурсов с Access Token
3. Implicit Flow (устаревший, для одностраничных приложений)
1. Client → User: переадресация на Authorization Server
2. User → Authorization Server: вход и подтверждение
3. Authorization Server → Client: Access Token прямо в фрагменте URL
Примеры реализации
Реализация Authorization Code Flow
// Frontend: запрос авторизации
const authUrl = 'https://auth.example.com/authorize';
const params = new URLSearchParams({
response_type: 'code',
client_id: 'your_client_id',
redirect_uri: 'https://yourapp.com/callback',
scope: 'read:profile write:data',
state: generateRandomString() // защита от CSRF
});
window.location.href = `${authUrl}?${params}`;
// Обработчик Callback
async function handleCallback(code, state) {
// Проверка state
if (state !== getStoredState()) {
throw new Error('Invalid state - CSRF attack detected');
}
// Обмен Code на Token
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'your_client_id',
client_secret: 'your_client_secret',
redirect_uri: 'https://yourapp.com/callback'
})
});
const tokens = await tokenResponse.json();
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
}
Backend: валидация токена
// Spring Boot Example
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/profile")
public ResponseEntity<UserProfile> getProfile(
@RequestHeader("Authorization") String authHeader) {
// Извлечение и валидация токена
String token = authHeader.replace("Bearer ", "");
if (!tokenValidator.isValid(token)) {
return ResponseEntity.status(401).build();
}
// Извлечение claims из токена
Claims claims = tokenValidator.getClaims(token);
String userId = claims.getSubject();
List<String> scopes = claims.get("scope", List.class);
// Проверка scope
if (!scopes.contains("read:profile")) {
return ResponseEntity.status(403).build();
}
UserProfile profile = userService.getProfile(userId);
return ResponseEntity.ok(profile);
}
@PostMapping("/data")
public ResponseEntity<?> createData(
@RequestHeader("Authorization") String authHeader,
@RequestBody DataRequest request) {
String token = authHeader.replace("Bearer ", "");
if (!tokenValidator.isValid(token)) {
return ResponseEntity.status(401).build();
}
Claims claims = tokenValidator.getClaims(token);
List<String> scopes = claims.get("scope", List.class);
// Проверка прав на запись
if (!scopes.contains("write:data")) {
return ResponseEntity.status(403).build();
}
Data data = dataService.create(request, claims.getSubject());
return ResponseEntity.ok(data);
}
}
Client Credentials Flow
# Python пример для взаимодействия сервер-сервер
import requests
def get_access_token():
token_url = "https://auth.example.com/token"
data = {
'grant_type': 'client_credentials',
'client_id': 'your_client_id',
'client_secret': 'your_client_secret',
'scope': 'api:read api:write'
}
response = requests.post(token_url, data=data)
response.raise_for_status()
token_data = response.json()
return token_data['access_token']
def api_call():
access_token = get_access_token()
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.example.com/data',
headers=headers
)
return response.json()
Обновление токена через Refresh Token
// Получение нового Access Token с помощью Refresh Token
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: 'your_client_id',
client_secret: 'your_client_secret'
})
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
localStorage.setItem('access_token', tokens.access_token);
if (tokens.refresh_token) {
localStorage.setItem('refresh_token', tokens.refresh_token);
}
return tokens.access_token;
} catch (error) {
// Refresh Token больше не действителен, нужна повторная авторизация
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
redirectToLogin();
}
}
Рекомендации по безопасности
State Parameter (защита от CSRF)
function generateState() {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
function storeState(state) {
sessionStorage.setItem('oauth_state', state);
}
function validateState(receivedState) {
const storedState = sessionStorage.getItem('oauth_state');
sessionStorage.removeItem('oauth_state');
return storedState === receivedState;
}
PKCE (Proof Key for Code Exchange)
// PKCE для мобильных и одностраничных приложений
function generatePKCE() {
const codeVerifier = generateRandomString(128);
const codeChallenge = base64urlEncode(sha256(codeVerifier));
return { codeVerifier, codeChallenge };
}
// Запрос авторизации с PKCE
const params = new URLSearchParams({
response_type: 'code',
client_id: 'your_client_id',
code_challenge: pkce.codeChallenge,
code_challenge_method: 'S256',
redirect_uri: 'https://yourapp.com/callback'
});
Преимущества и недостатки
Преимущества
- Безопасность: пароль не передается третьим сторонам
- Стандартизация: широко распространен и хорошо документирован
- Гибкость: различные потоки для разных сценариев использования
- Масштабируемость: централизованная авторизация для множества сервисов
- Отзыв: токены можно аннулировать в любой момент
Недостатки
- Сложность: больше шагов, чем простая аутентификация
- Производительность: дополнительные сетевые запросы для обновления токенов
- Управление состоянием: клиент должен управлять статусом токенов
- Риски безопасности: неправильная реализация может привести к уязвимостям
Часто встречающиеся вопросы на экзаменах
-
В чем разница между OAuth 2.0 и OpenID Connect? OAuth 2.0 используется для авторизации, OpenID Connect добавляет аутентификацию (проверку личности).
-
Объясните Authorization Code Flow! Client переадресовывает пользователя на Authorization Server, получает код, обменивает код на токен.
-
Когда используется Client Credentials Flow? Для взаимодействия сервер-сервер без участия пользователя.
-
Для чего служит State Parameter? Защита от CSRF путем проверки, что запрос был инициирован клиентом.
Основные ресурсы
- https://tools.ietf.org/html/rfc6749
- https://oauth.net/2/
- https://auth0.com/docs/authorization-framework/overview



