인증 API
로그인, 세션 갱신, 로그아웃, 사용자 정보 조회
개요
SCH Web Portal의 인증은 HttpOnly 쿠키 기반 세션 방식을 사용합니다.
로그인 성공 시 SCH 서버로부터 획득한 jsessionid, ecd_key 등을 AES-256-GCM으로 암호화한 토큰을 sch_session 쿠키로 발급합니다.
Authorization 헤더가 없습니다. 모든 인증은 sch_session 쿠키를 통해 이루어지며, 브라우저가 자동으로 쿠키를 포함합니다.
엔드포인트 목록
| 메서드 | 경로 | 인증 필요 | 설명 |
|---|---|---|---|
POST | /api/auth/login/ | 아니오 | SCH 로그인 + 세션 쿠키 발급 |
POST | /api/auth/refresh/ | 쿠키 | 세션 만료시간 연장 |
POST | /api/auth/logout/ | 아니오 | 쿠키 삭제 |
GET | /api/auth/me/ | 쿠키 | 세션 복원용 사용자 정보 |
GET | /api/auth/turnstile/ | 아니오 | 로그인 화면용 Turnstile 공개 설정 |
POST /api/auth/login/
SCH 통합정보시스템에 인증하고, 세션을 암호화하여 HttpOnly 쿠키로 발급합니다.
요청
Body (application/json)
{
"std_no": "20201234",
"password": "your_password",
"turnstile_token": "0.abc123..."
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
std_no | string | O | 학번 (공백 자동 제거) |
password | string | O | 비밀번호 |
turnstile_token | string | 조건부 | Cloudflare Turnstile 위젯이 발급한 토큰. 서버에 Turnstile 키가 설정된 경우에만 필수 |
Turnstile 검증은 자격증명을 SCH 서버로 릴레이하기 전에 수행됩니다. 검증을 뒤로 미루면 봇이 던진 시도까지 학교 서버에 그대로 전달되기 때문입니다.
응답
200 OK
{
"user": {
"std_no": "20201234",
"name": "홍길동"
},
"expires_at_ms": 1700007200000
}동시에 아래 쿠키가 설정됩니다:
Set-Cookie: sch_session=<token>; Path=/; HttpOnly; SameSite=Lax; Max-Age=7200| 응답 필드 | 타입 | 설명 |
|---|---|---|
user.std_no | string | 학번 |
user.name | string | 한국어 이름 |
expires_at_ms | number | 세션 만료 시각 (Unix milliseconds) |
세션 TTL은 **7200초 (2시간)**이며, 쿠키는 HttpOnly이므로 JavaScript에서 읽을 수 없습니다.
에러 응답
| 에러 코드 | HTTP | 설명 |
|---|---|---|
MISSING_FIELDS | 400 | std_no 또는 password 누락 혹은 빈 값 |
CAPTCHA_FAILED | 403 | Turnstile 토큰이 없거나 위조·만료·재사용됨 |
INVALID_CREDENTIALS | 401 | 학번 또는 비밀번호 오류 |
2FA_REQUIRED | 401 | 이중인증(2FA) 계정 — 현재 미지원 |
AUTH_FAILED | 500 | 기타 SCH 인증 오류 |
CAPTCHA_UNAVAILABLE | 503 | Cloudflare 검증 서버에 연결 실패 (TURNSTILE_FAIL_OPEN=true면 검증을 건너뛰고 로그인 진행) |
SCH_UNAVAILABLE | 503 | SCH 서버 연결 실패 |
// 예: 학번/비밀번호 오류
{
"error": "INVALID_CREDENTIALS",
"message": "학번 또는 비밀번호가 올바르지 않습니다."
}POST /api/auth/refresh/
현재 세션을 갱신하여 만료 시간을 현재 시각 기준 +2시간으로 연장합니다.
요청
Body 없음. 쿠키 sch_session이 포함되어야 합니다.
응답
200 OK
{
"expires_at_ms": 1700014400000
}갱신된 토큰으로 sch_session 쿠키가 덮어씌워집니다.
에러 응답
| 에러 코드 | HTTP | 설명 |
|---|---|---|
INVALID_TOKEN | 401 | 쿠키 없음, 만료, 또는 복호화 실패 |
세션 쿠키는 발급 시점의 클라이언트 IP와 User-Agent에 바인딩됩니다. 다른 IP나 브라우저에서 재사용하면 복호화가 실패하여 INVALID_TOKEN이 반환됩니다.
POST /api/auth/logout/
sch_session 쿠키를 만료 처리합니다.
요청
Body 없음.
응답
200 OK
{
"ok": true
}Set-Cookie: sch_session=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT쿠키가 없어도 200을 반환합니다. SCH 서버에 별도로 로그아웃 요청을 보내지 않습니다.
GET /api/auth/me/
현재 로그인된 사용자 정보와 세션 만료 시간을 반환합니다.
페이지 재방문 시 로컬 상태를 복원할 때 사용합니다.
요청
쿠키 sch_session 포함.
응답
200 OK
{
"std_no": "20201234",
"name": "홍길동",
"expires_at_ms": 1700007200000
}| 필드 | 타입 | 설명 |
|---|---|---|
std_no | string | 학번 |
name | string | 이름 |
expires_at_ms | number | 세션 만료 시각 (Unix ms) |
에러 응답
| 에러 코드 | HTTP | 설명 |
|---|---|---|
UNAUTHORIZED | 401 | 세션 없음 또는 만료 |
GET /api/auth/turnstile/
로그인 화면이 Turnstile 위젯을 그리는 데 필요한 공개 설정을 반환합니다. 인증이 필요 없습니다.
응답
200 OK
{
"enabled": true,
"site_key": "0x4AAAAAAA..."
}| 필드 | 타입 | 설명 |
|---|---|---|
enabled | boolean | 서버에 사이트키·시크릿키가 모두 설정됐는지. false면 로그인에 turnstile_token이 필요 없습니다 |
site_key | string | 위젯 렌더링용 공개 사이트키. enabled가 false면 빈 문자열 |
사이트키를 프론트엔드 빌드에 박지 않고 여기서 내려줍니다. 키 관리 지점을 서버 .env 한 곳으로 유지해, 빌드 재배포 없이 키를 교체할 수 있게 하기 위함입니다.
키가 없는 로컬 개발 환경에서는 enabled: false가 되어 Turnstile 없이 로그인이 그대로 동작합니다.
세션 토큰 내부 구조
[Binary Layout]
version(1 byte) || kid(1 byte) || nonce(12 bytes) || ciphertext+GCM_tag(N+16 bytes)
Base64URL 인코딩하여 쿠키에 저장암호화 방식: AES-256-GCM
키 파생: HKDF-SHA256 (salt="sch-portal-v1", info="session-v1")
AAD (Additional Authenticated Data): HMAC-SHA256("ctx-binding", ip + "|" + user_agent)
AAD에 IP와 User-Agent가 바인딩되어 있어, 다른 환경에서 토큰을 재사용하면 GCM 인증이 실패합니다.
세션 페이로드 구조:
{
"std_no": "20201234", # 학번
"user_no": "U000001234", # SCH 내부 사용자 번호
"name": "홍길동", # 이름
"jsessionid": "ABC123...", # SCH 서버 세션 ID
"ecd_key": "ENCKEY...", # TRANSACTION_ECDKEY
"dept_cd": "CS", # 학과 코드 (없으면 "")
"password": "...", # 웹메일 SSO 전용 (아래 참고)
"mail_cookies": {}, # 웹메일 세션 쿠키 (최초엔 빈 dict)
"expired": 1700007200 # 만료 Unix timestamp
}이 페이로드는 절대 클라이언트에 노출되지 않으며, 서버에서만 복호화합니다.
페이로드에 비밀번호가 있는 이유
웹메일(sso.sch.ac.kr)은 종합정보시스템과 인증 체계가 달라 별도 로그인 체인이 필요한데, 매 요청 새로 발급되는 RSA 키로 자격증명을 암호화하는 방식이라 재사용 가능한 토큰이 없습니다. 체인이 왕복 3번이라 로그인 응답을 그만큼 늦추지 않으려고, 로그인 시점에 미리 태우지 않고 메일 페이지 첫 진입 때 세션 속 비밀번호로 SSO를 수립합니다.
비밀번호는 이 페이로드에만 존재합니다. AES-256-GCM으로 암호화되고 IP + User-Agent에 AAD 바인딩된 HttpOnly 쿠키 안이며, 서버 DB·로그에는 남지 않습니다.
자세한 흐름은 웹메일 API 문서를 참고하세요.
페이로드를 재암호화하는 곳
세션 값을 늘리면 아래 세 곳을 모두 확인해야 합니다.
| 위치 | 시점 |
|---|---|
views/auth.py LoginView | 로그인 |
views/auth.py RefreshView | 세션 연장 |
views/mail.py _renew_session() | 메일 쿠키 갱신 |
mail.py는 expired를 그대로 두고 남은 시간만큼만 쿠키 수명을 줍니다. 여기서 TTL을 새로 주면 세션 연장이 아닌데도 연장돼 버립니다.