Система единого входа (Single Sign-On) для проектов ITS TECH. Предоставляет централизованную аутентификацию и авторизацию через gRPC API с поддержкой JWT токенов.
- Возможности
- Архитектура
- Требования
- Варианты запуска
- API Reference
- Особенности
- Конфигурация
- Тестирование
- Регистрация пользователей - создание новых учетных записей с валидацией данных
- Вход в систему - аутентификация с получением JWT токена для конкретного приложения
- Обновление токена - получение нового JWT токена без повторной аутентификации
- Мультиприложенность - поддержка нескольких приложений с разными секретами для токенов
- Информация о пользователе - получение данных профиля (сам пользователь или администратор)
- Обновление пароля - изменение пароля пользователем или администратором
- Удаление пользователя - удаление аккаунта (самостоятельно или администратором)
- Список пользователей - получение списка всех пользователей (только для администраторов)
- Проверка прав администратора - определение статуса администратора пользователя
- Административный доступ - расширенные права для управления пользователями
- Безопасность - многоуровневая система проверки прав доступа
- Ping - проверка доступности сервиса
- Health checks - мониторинг состояния сервиса
- Язык: Go 1.25+
- Протокол: gRPC
- База данных: PostgreSQL 16
- Аутентификация: JWT (JSON Web Tokens)
- Хеширование паролей: bcrypt
- Конфигурация: Viper (поддержка .env файлов и переменных окружения)
- Миграции: migrate/migrate
- Контейнеризация: Docker, Docker Compose
sso/
├── cmd/sso/ # Точка входа приложения
├── internal/
│ ├── app/ # Инициализация приложения
│ ├── config/ # Конфигурация
│ ├── domain/ # Доменные модели
│ ├── grpc/ # gRPC handlers
│ ├── lib/ # Утилиты (JWT, генерация)
│ ├── logs/ # Логирование
│ ├── repository/ # Работа с БД
│ └── services/ # Бизнес-логика
├── migrations/ # SQL миграции
├── tests/ # Тесты
├── docker-compose.yml # Docker Compose конфигурация
└── Dockerfile # Docker образ
- Docker 20.10+
- Docker Compose 2.0+
- Go 1.25+
- PostgreSQL 16+
- migrate/migrate (для миграций)
Самый простой способ запуска всей инфраструктуры одним командой.
- Создайте файл
.envв корне проекта:
# Application
APP_NAME=sso
ENV=local
# GRPC
GRPC_PORT=8080
GRPC_TIMEOUT=10s
# Database
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_EXTERNAL_PORT=5436
POSTGRES_DB=myapp_db
POSTGRES_USER=dbuser
POSTGRES_PASSWORD=dbpass123
# JWT
JWT_SECRET=my-secret
JWT_TOKEN_TTL=12h- Запустите все сервисы:
docker-compose up -dЭта команда автоматически:
- Создаст сеть для сервисов
- Запустит PostgreSQL с проверкой здоровья
- Выполнит миграции базы данных
- Запустит SSO приложение
# Просмотр логов
docker-compose logs -f sso
# Остановка всех сервисов
docker-compose down
# Остановка с удалением данных БД
docker-compose down -v
# Пересборка образа приложения
docker-compose up -d --build sso
# Перезапуск сервиса
docker-compose restart sso
# Просмотр статуса
docker-compose ps- SSO gRPC:
localhost:8080(порт настраивается черезGRPC_PORT) - PostgreSQL:
localhost:5436(внешний порт настраивается черезPOSTGRES_EXTERNAL_PORT)
Если вы хотите запустить приложение локально, а базу данных в Docker:
docker run --name=sso-db \
-e POSTGRES_PASSWORD='qwerty' \
-e POSTGRES_USER='postgres' \
-e POSTGRES_DB='postgres' \
-p 5436:5432 \
-d postgres:16-alpine3.18migrate -path ./migrations \
-database 'postgres://postgres:qwerty@localhost:5436/postgres?sslmode=disable' \
upENV=local
GRPC_PORT=8080
GRPC_TIMEOUT=10s
POSTGRES_HOST=localhost
POSTGRES_PORT=5436
POSTGRES_EXTERNAL_PORT=5436
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=qwerty
JWT_SECRET=my-secret
JWT_TOKEN_TTL=12hgo run cmd/sso/main.goДля разработки с локальной PostgreSQL:
- Установленная PostgreSQL 16
- Созданная база данных
- Установленные зависимости Go
-- Создание базы данных
CREATE DATABASE sso_db;
-- Создание пользователя (опционально)
CREATE USER sso_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE sso_db TO sso_user;migrate -path ./migrations \
-database 'postgres://sso_user:your_password@localhost:5432/sso_db?sslmode=disable' \
upENV=local
GRPC_PORT=8080
GRPC_TIMEOUT=10s
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_EXTERNAL_PORT=5432
POSTGRES_DB=sso_db
POSTGRES_USER=sso_user
POSTGRES_PASSWORD=your_password
JWT_SECRET=my-secret
JWT_TOKEN_TTL=12h# Установка зависимостей
go mod download
# Запуск приложения
go run cmd/sso/main.goДля production окружения рекомендуется:
- Использовать Docker Compose с production конфигурацией
- Настроить переменные окружения через secrets management
- Использовать внешнюю PostgreSQL для высокой доступности
- Настроить мониторинг и логирование
- Использовать load balancer перед gRPC сервером
Пример production .env:
ENV=prod
GRPC_PORT=8080
GRPC_TIMEOUT=10s
POSTGRES_HOST=postgres-prod.example.com
POSTGRES_PORT=5432
POSTGRES_EXTERNAL_PORT=5432
POSTGRES_DB=sso_prod
POSTGRES_USER=sso_prod_user
POSTGRES_PASSWORD=<secure_password_from_secrets>
JWT_SECRET=<secure_secret_from_secrets>
JWT_TOKEN_TTL=1hВсе endpoints доступны через gRPC протокол на порту, указанном в GRPC_PORT.
Проверка доступности сервиса.
Request:
EmptyResponse:
EmptyПример использования:
_, err := client.Ping(ctx, &emptypb.Empty{})Регистрация нового пользователя в системе.
Request:
message RegisterRequest {
string login = 1; // Уникальный логин пользователя
string password = 2; // Пароль пользователя
string email = 3; // Email адрес
string full_name = 4; // Полное имя пользователя
}Response:
message RegisterResponse {
int64 user_id = 1; // ID созданного пользователя
}Ошибки:
InvalidArgument- некорректные данные запросаInternal- ошибка при создании пользователя
Особенности:
- Пароль автоматически хешируется с помощью bcrypt
- Логин должен быть уникальным
- Email должен быть валидным форматом
Аутентификация пользователя и получение JWT токена.
Request:
message LoginRequest {
int32 app_id = 1; // ID приложения (0 для SSO, >0 для других приложений)
string login = 2; // Логин пользователя
string password = 3; // Пароль пользователя
}Response:
message LoginResponse {
string token = 1; // JWT токен для приложения
}Ошибки:
NotFound- пользователь или приложение не найденыInternal- неверный логин или пароль
Особенности:
- Токен для SSO (app_id=0) содержит только
uidиexp - Токен для других приложений содержит
uid,login,email,app_id,exp - Время жизни токена настраивается через
JWT_TOKEN_TTL - Токен подписывается секретом приложения
Получение информации о пользователе.
Request:
message UserInfoRequest {
int64 user_id = 1; // ID пользователя
}Response:
message User {
int64 user_id = 1;
string login = 2;
string email = 3;
string full_name = 4;
bool is_admin = 5;
google.protobuf.Timestamp create_at = 6;
google.protobuf.Timestamp update_at = 7;
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенPermissionDenied- пользователь не имеет прав на просмотр данного профиляInternal- ошибка при получении информации
Особенности:
- Пользователь может просмотреть только свой профиль
- Администратор может просмотреть любой профиль
- Требуется валидный JWT токен в заголовке
Authorization: Bearer <token>
Проверка прав администратора у пользователя.
Request:
message IsAdminRequest {
int64 user_id = 1; // ID пользователя для проверки
}Response:
message IsAdminResponse {
bool is_admin = 1; // Статус администратора
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенPermissionDenied- обычный пользователь пытается проверить другого пользователяNotFound- пользователь не найден
Особенности:
- Пользователь может проверить только свои права
- Администратор может проверить права любого пользователя
- Требуется валидный JWT токен
Обновление пароля пользователя.
Request:
message UpdatePasswordRequest {
int64 user_id = 1; // ID пользователя
string new_password = 2; // Новый пароль
}Response:
message UpdatePasswordResponse {
string message = 1; // Сообщение об успехе
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенPermissionDenied- обычный пользователь пытается изменить чужой парольNotFound- пользователь не найденInternal- ошибка при обновлении пароля
Особенности:
- Пользователь может изменить только свой пароль
- Администратор может изменить пароль любого пользователя
- Новый пароль автоматически хешируется
- После обновления старый пароль становится недействительным
Удаление пользователя из системы.
Request:
message RemoveUserRequest {
int64 user_id = 1; // ID пользователя для удаления
}Response:
message RemoveUserResponse {
string message = 1; // Сообщение об успехе
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенPermissionDenied- обычный пользователь пытается удалить другого пользователяNotFound- пользователь не найден (может быть успешно, если пользователь уже удален)
Особенности:
- Пользователь может удалить только свой аккаунт
- Администратор может удалить любого пользователя
- После удаления пользователь не сможет войти в систему
- Удаление выполняется "мягко" (без удаления из БД, с пометкой удаленным)
Обновление JWT токена без повторной аутентификации.
Request:
EmptyResponse:
message UpdateTokenResponse {
string token = 1; // Новый JWT токен
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенInvalidArgument- токен не требует обновления или отсутствуетInternal- ошибка при создании нового токена
Особенности:
- Для SSO токенов (app_id=0) - создает новый SSO токен
- Для токенов приложений - обновляет токен приложения
- Новый токен имеет тот же срок жизни, что указан в конфигурации
- Старый токен остается валидным до истечения срока действия
Получение списка всех пользователей (только для администраторов).
Request:
EmptyResponse:
message Users {
repeated User users = 1; // Список всех пользователей
}Ошибки:
Unauthenticated- отсутствует или невалидный JWT токенPermissionDenied- запрашивающий пользователь не является администратором
Особенности:
- Доступно только администраторам
- Возвращает полный список пользователей системы
- Включает информацию о статусе администратора для каждого пользователя
Большинство endpoints требуют JWT токен в заголовке запроса:
Authorization: Bearer <jwt_token>
Токен должен быть валидным и не истекшим. Для SSO токенов используется секрет из JWT_SECRET, для токенов приложений - секрет конкретного приложения.
SSO поддерживает работу с несколькими приложениями одновременно:
- SSO (app_id = 0): Специальное приложение для управления пользователями. Токены подписываются
JWT_SECRET. - Другие приложения (app_id > 0): Каждое приложение имеет свой секрет для подписи токенов. Информация хранится в таблице
apps.
Пример:
// Логин для SSO
loginResp, _ := client.Login(ctx, &ssov1.LoginRequest{
AppId: 0, // SSO
Login: "user",
Password: "pass",
})
// Логин для приложения с ID=1
appLoginResp, _ := client.Login(ctx, &ssov1.LoginRequest{
AppId: 1, // Другое приложение
Login: "user",
Password: "pass",
})Обычный пользователь:
- Может просматривать только свой профиль
- Может изменять только свой пароль
- Может удалить только свой аккаунт
- Может проверить только свои права администратора
Администратор:
- Может просматривать профиль любого пользователя
- Может изменять пароль любого пользователя
- Может удалить любого пользователя
- Может проверять права любого пользователя
- Может получить список всех пользователей
Проверка прав:
isAdminResp, _ := client.IsAdmin(ctx, &ssov1.IsAdminRequest{
UserId: userID,
})
if isAdminResp.IsAdmin {
// Пользователь является администратором
}- Все пароли хешируются с помощью bcrypt перед сохранением в базу данных
- Пароли никогда не передаются в открытом виде после сохранения
- При обновлении пароля автоматически создается новый хеш
Структура SSO токена (app_id=0):
{
"uid": "123",
"exp": 1234567890
}Структура токена приложения (app_id>0):
{
"uid": "123",
"login": "user",
"email": "user@example.com",
"app_id": 1,
"exp": 1234567890
}Особенности:
- Токены имеют ограниченный срок жизни (настраивается через
JWT_TOKEN_TTL) - Токены подписываются секретом приложения (HMAC-SHA256)
- Истекшие токены отклоняются с ошибкой
Unauthenticated
Система поддерживает приоритет конфигурации:
- Переменные окружения (высший приоритет)
- Файл .env (низкий приоритет)
Это позволяет легко переопределять настройки для разных окружений.
При запуске через Docker Compose миграции выполняются автоматически:
- PostgreSQL запускается и проходит health check
- Выполняется сервис миграций
- После успешных миграций запускается SSO приложение
Система использует структурированное логирование (slog) с поддержкой разных уровней:
- local/dev: Debug уровень
- prod: Info уровень
Логи включают контекстные поля (операция, пользователь, время и т.д.)
PostgreSQL имеет встроенный health check, который проверяет готовность базы данных перед запуском зависимых сервисов.
| Переменная | Описание | Значение по умолчанию | Обязательная |
|---|---|---|---|
ENV |
Окружение (local/dev/prod) | local |
Нет |
GRPC_PORT |
Порт gRPC сервера | 8080 |
Нет |
GRPC_TIMEOUT |
Таймаут gRPC запросов | 10s |
Нет |
POSTGRES_HOST |
Хост PostgreSQL | localhost |
Да |
POSTGRES_PORT |
Внутренний порт PostgreSQL | 5432 |
Да |
POSTGRES_EXTERNAL_PORT |
Внешний порт PostgreSQL | 5432 |
Да |
POSTGRES_DB |
Имя базы данных | - | Да |
POSTGRES_USER |
Пользователь БД | - | Да |
POSTGRES_PASSWORD |
Пароль БД | - | Да |
JWT_SECRET |
Секрет для подписи SSO токенов | - | Да |
JWT_TOKEN_TTL |
Время жизни токена | 12h |
Нет |
# Application
ENV=local
# GRPC
GRPC_PORT=8080
GRPC_TIMEOUT=10s
# Database
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_EXTERNAL_PORT=5436
POSTGRES_DB=myapp_db
POSTGRES_USER=dbuser
POSTGRES_PASSWORD=dbpass123
# JWT
JWT_SECRET=my-secret
JWT_TOKEN_TTL=12hПроект содержит комплексный набор тестов, покрывающий все основные функции.
# Все тесты
go test ./tests/...
# Конкретный тест
go test ./tests/... -run TestRegisterLogin_Login_HappyPath
# С verbose выводом
go test ./tests/... -vТесты покрывают следующие сценарии:
- ✅ Успешная регистрация пользователя
- ✅ Успешный вход с получением JWT токена
- ✅ Проверка корректности данных в токене
- ✅ Пользователь получает свою информацию
- ✅ Администратор получает свою информацию
- ✅ Администратор получает информацию другого пользователя
- ✅ Проверка прав доступа
- ✅ Пользователь обновляет свой пароль
- ✅ Администратор обновляет пароль пользователя
- ✅ Проверка работы нового пароля
- ✅ Администратор удаляет пользователя
- ✅ Пользователь удаляет свой аккаунт
- ✅ Администратор удаляет несуществующего пользователя
- ✅ Проверка обычного пользователя (не админ)
- ✅ Проверка администратора
- ✅ Обновление токена пользователя
- ✅ Обычный пользователь не может получить список
- ✅ Администратор получает список всех пользователей
По умолчанию в тестах используется:
- SSO App ID: 0
- Test App ID: 1
- Test App Secret: "test-secret"
- Admin Login: "admin"
- Admin Password: "admin_pass"
- SSO Secret: "my-secret"
Проект следует принципам Clean Architecture:
- cmd/ - точки входа приложения
- internal/app/ - инициализация и сборка приложения
- internal/config/ - конфигурация
- internal/domain/ - доменные модели
- internal/grpc/ - gRPC handlers и валидация
- internal/lib/ - утилиты (JWT, генерация)
- internal/repository/ - слой работы с данными
- internal/services/ - бизнес-логика
- migrations/ - SQL миграции
- tests/ - интеграционные тесты
# Создание новой миграции
migrate create -ext sql -dir ./migrations -seq add_new_table
# Применить миграции
migrate -path ./migrations \
-database 'postgres://user:pass@localhost:5432/db?sslmode=disable' \
up
# Откатить последнюю миграцию
migrate -path ./migrations \
-database 'postgres://user:pass@localhost:5432/db?sslmode=disable' \
down- Запустите PostgreSQL через Docker Compose:
docker-compose up -d postgres- Выполните миграции:
migrate -path ./migrations \
-database 'postgres://dbuser:dbpass123@localhost:5436/myapp_db?sslmode=disable' \
up-
Настройте
.envдля локального запуска -
Запустите приложение:
go run cmd/sso/main.goРешение:
- Проверьте, что PostgreSQL запущен:
docker-compose ps - Проверьте логи:
docker-compose logs postgres - Убедитесь, что переменные окружения настроены правильно
- В Docker используйте имя сервиса
postgresвместоlocalhost
Решение:
- Проверьте подключение к БД
- Убедитесь, что миграции выполняются до запуска приложения
- Проверьте логи миграций:
docker-compose logs migrate
Решение:
- Убедитесь, что используется правильный секрет для приложения
- Проверьте срок действия токена
- Убедитесь, что токен правильно передается в заголовке
Authorization: Bearer <token>
Решение:
- Проверьте, что пользователь имеет необходимые права (администратор)
- Убедитесь, что пользователь пытается получить доступ только к своим данным
- Проверьте, что JWT токен валиден и принадлежит правильному пользователю
Внутренний проект ITS TECH
Для вопросов и предложений обращайтесь к команде разработки ITS TECH.