GN Gerege Nexus
GitHub Нэвтрэх

SSO холбоос: нэг суулгац нөгөөгөө таниулах

Gerege Nexus эхнээсээ identity provider байсан: OAuth2 authorization server бөгөөд OpenID Connect provider (backend/internal/platform/ssoprovider). Түүн дээр бүртгэлтэй апп, гадаад систем нь энэ платформоор дамжуулан хүнийг танидаг.

Одоо нөгөө хагас нь бий: суулгац өөрөө өөр провайдерийн клиент байж болно (backend/internal/platform/ssoclient). Хоёр хагас нь бие биеэсээ хамааралгүй. Нэг суулгац дараах гурван байдлын аль нэгээр ажиллана:

Тохиргоо Юу болох вэ
SSO_CLIENT_ISSUER хоосон Урьдын адил. Суулгац хүнийг өөрөө таниулж, өөрөө identity тарааж өгнө.
SSO_CLIENT_ISSUER тохируулсан Суулгац хүнийг өөрөө танихаа болино — нэвтрэлт провайдер дээр болно. Апп-уудад identity тараах чадвар нь хэвээр.
Хоёулаа Аймгийн суулгац улсын нэгдсэн рүү дээшээ холбогдож, өөрөө өөрийн дээр суусан аппуудад identity өгнө. Энэ хэлбэрийг зорьж хийсэн.

Клиент болгож тохируулах

Хамгийн бага тохиргоо нь хоёр хувьсагч:

SSO_CLIENT_ISSUER=https://nexus.gerege.mn
SSO_CLIENT_ID=aimag-office

Бусад нь заавал биш:

Хувьсагч Утга
SSO_CLIENT_SECRET Хоосон бол public клиент — солилцоог PKCE дангаараа баталгаажуулна. Суулгац бол сервер тул нууц үг барьж чадна; провайдер олгосон бол тавь.
SSO_CLIENT_PROVIDER_NAME Нэвтрэх дэлгэц дээр провайдерийг юу гэж нэрлэх. Хоосон бол issuer-ийн host.
SSO_CLIENT_SCOPES Өгөгдмөл openid profile email. Жагсаалтад байхгүй байсан ч openid нэмэгдэнэ: түүнгүйгээр id_token огт ирэхгүй.
SSO_CLIENT_REDIRECT_URI Өгөгдмөл ${SSO_ISSUER}/api/v1/auth/sso/callback. Хөгжүүлэлтэд API :8080, вэб :3000 тул гараар зааж өгнө.
SSO_CLIENT_POST_LOGOUT_REDIRECT_URI Гарсны дараа хаана буцаж очих. Өгөгдмөл ${SSO_ISSUER}/.
SSO_CLIENT_TENANT Провайдерийн баталгаажуулсан ч энд бүртгэлгүй хүнийг ямар байгууллагад үүсгэх. Хоосон бол хэнийг ч үүсгэхгүй — танихгүй хүнийг татгалзана.
SSO_CLIENT_LOCAL_LOGIN true бол эндэх нууц үг, eID нэвтрэлт хамт ажиллана. Өгөгдмөл false. Энэ бол провайдер унасан үед буцаж орох баталгаатай зам.

Production дээр SSO_CLIENT_ISSUER заавал HTTPS байх ба SSO_CLIENT_ID шаардлагатай — эс бөгөөс сервер эхлэхээсээ өмнө татгалзана. Loopback дээр http:// зөвшөөрөгдөнө: нэг машин дээр хоёр суулгацыг бие бие рүү нь чиглүүлж турших замыг нээлттэй байлгах гэсэн санаа.

Провайдер дээр юу бүртгүүлэх вэ

Провайдер нь өөр Gerege Nexus бол Хөгжүүлэгч → Аппликейшн дотроос клиент үүсгээд:

Хоёр хаягийн host нь провайдерийн OAUTH_REDIRECT_HOSTS жагсаалтад байх ёстой. Тэр жагсаалт нь дэд домэйныг өвлүүлдэггүй, яг таарч байж болно.


Ажиллах урсгал

Нэвтрэх

  1. Хүн клиент суулгац дээр /login дээр ирнэ. Дэлгэц /api/v1/auth/sso/config-оос энэ суулгац хэрхэн нэвтрүүлдгийг асууна.
  2. Холбоостой бол дэлгэц юу ч асуухгүй — хөтчийг /api/v1/auth/sso/start руу илгээнэ.
  3. start нь PKCE verifier, state, nonce үүсгээд гурвуулангийнх нь хамт очих газрыг (next) богино хугацааны HttpOnly cookie-д хийж, провайдерийн authorization endpoint руу чиглүүлнэ. Тэр хаягийг провайдерийн өөрийнх нь discovery баримтаас авна, хүсэлтээс биш.
  4. Провайдер дээр хүн нэвтэрч (эсвэл аль хэдийн нэвтэрсэн бол шууд) кодтой буцаж /api/v1/auth/sso/callback дээр ирнэ.
  5. Callback нь cookie-н state-тэй тулгаж, кодыг token endpoint дээр солиод ирсэн id_token-ийг провайдерийн JWKS-ээр шалгана: RS256 гэдгийг, гарын үсгийг, iss, aud, azp, exp, iat, nonce бүгдийг.
  6. Баталгаажсан хүнийг эндэх бүртгэлтэй холбож (доор үзнэ үү), session үүсгэж, next рүү буцаана.

Гарах

  1. Хөтөч /api/v1/auth/logout дуудна. Эндэх session тэр дороо хүчингүй болно.
  2. Хариу нь end_session_url авчирна — провайдерийн end_session_endpoint дээр client_id, id_token_hint, post_logout_redirect_uri-тай.
  3. Хөтөч тийш очно. Провайдер өөрийн session-ээ хааж, бүртгэлтэй post_logout_redirect_uri дээр буцаана.

Гурав дахь алхам байхгүй бол хүн "гарлаа" гээд "нэвтрэх" дархад шууд буцаж ордог — өөрөөр хэлбэл гараагүй.


Хэн болохыг эндэх бүртгэлтэй холбох

Түлхүүр нь (issuer, subject), и-мэйл биш. И-мэйл бол провайдер өөрчилж болох шошго: гарсан ажилтны хаягийг өөр хүнд өгвөл тэр хүн түүний бүртгэлийг өвлөнө. subject бол провайдерийн тогтвортой байлгахаа амласан, дахин хуваарилахгүй гэсэн цорын ганц claim. Холбоос нь user_sso_identities хүснэгтэд суудаг.

Анх удаа ирсэн хүнийг гурван шатаар шийднэ:

  1. Холбоос байвал — тэр бүртгэл.
  2. Провайдерийн баталгаажуулсан и-мэйлтэй адил бүртгэл эндээс олдвол — түүнийг өвлүүлж, холбоосыг бичнэ. Энэ бол ажиллаж байгаа суулгацыг холбоосонд оруулах зам. И-мэйл нь баталгаажсан байх ёстой: эс бөгөөс профайл дээрээ хаяг бичээд хэн ч аль ч бүртгэлийг нэхэж болно.
  3. SSO_CLIENT_TENANT тохируулсан бол — тэр байгууллагад шинэ бүртгэл үүсгэнэ. Тохируулаагүй бол баталгаажсан ч танихгүй хүнийг татгалзана: ихэнх суулгац дээр гишүүнчлэл бол зориудын үйлдэл.

Ингэж үүссэн бүртгэлд нууц үгээр нэвтрэх зам байхгүй. password_hash баганад энэ процесс өөрөө хормын дараа мэдэхээ болих санамсаргүй утга суудаг — subject-ээс гаргаж авсан утга байсан бол дахин тооцоолж болох тул тэр нь холбоосон бүртгэлийн ажиллах нууц үг болох байсан.


Google-ээр нэвтрэх

Энэ бол дээрхээс өөр зүйл. Холбоос (SSO_CLIENT_ISSUER) нь эндэх нэвтрэлтийг хаадаг; Google бол платформын өөрийнх нь нэвтрэх дэлгэц дээр eID-ийн хажууд нэмэгдэх бас нэг товч бөгөөд юуг ч хаахгүй. Доор нь протокол нь ижил — Google бол ердийн OpenID Connect провайдер — тул discovery, PKCE, id_token шалгалт бүгд нэг код (ssoclient).

GOOGLE_LOGIN_CLIENT_ID=…apps.googleusercontent.com
GOOGLE_LOGIN_CLIENT_SECRET=…

Google дээр ${SSO_ISSUER}/api/v1/auth/google/callback-ийг redirect URI-аар бүртгүүлнэ.

Хувьсагч Утга
GOOGLE_LOGIN_REDIRECT_URI Өгөгдмөл ${SSO_ISSUER}/api/v1/auth/google/callback. Хөгжүүлэлтэд гараар.
GOOGLE_LOGIN_TENANT Google баталгаажуулсан ч энд бүртгэлгүй хүнийг үүсгэх байгууллага. Хоосон бол хэнийг ч үүсгэхгүй.
GOOGLE_LOGIN_ALLOWED_DOMAINS Зөвшөөрөгдөх и-мэйл домэйнууд, таслалаар (gerege.mn,dgov.mn).

Яагаад GOOGLE_OAUTH_CLIENT_ID-г дахин ашиглаагүй вэ

Drive болон Meet холбогчид тэр хосыг аль хэдийн хэрэглэдэг. Нэг Google төсөл хоёуланг нь олгодог бөгөөд утга нь ижил байж болно — гэхдээ энэ нь операторын зориудын үйлдэл байх ёстой: баримтын холбогч тохируулсан нь чимээгүйхэн шинэ нэвтрэх хаалга нээж болохгүй.

Хэн орж чадах вэ

Гурван шүүлтүүр дараалан ажиллана:

  1. Баталгаажаагүй и-мэйлийг татгалзана. Хаяг бол эндэх бүртгэлтэй тааруулах түлхүүр тул email_verified худал бол профайл дээрээ хаяг бичсэн хэн ч өөрийнх биш бүртгэлийг нэхэж болно.
  2. Домэйны жагсаалт (тохируулсан бол).
  3. Бүртгэл олдох эсэх. GOOGLE_LOGIN_TENANT хоосон бол Google-ийн баталгаажуулсан хаягтай аль хэдийн байгаа бүртгэл рүү л орно. Тохируулбал Google бүртгэлтэй хэн ч энд бүртгэл авч чадна — тиймээс домэйны жагсаалттай хамт тохируулна.

Бүртгэл нь (issuer, subject)-ээр холбогдоно, яг холбоосын нэгэн адил (user_sso_identities, issuer нь https://accounts.google.com).

Гарах

Google session-ыг таслахгүй. Эндээс гарсан хүнийг Google-ээс нь гаргах нь энэ платформын хийх ажил биш — тиймээс id_token cookie ч хадгалахгүй.


Провайдер тал: RP-initiated logout

end_session_endpoint-ийг discovery баримт бичигдсэн цагаасаа зарладаг байсан ч тэр эндпойнт байхгүй байв. Одоо /oauth2/logout дээр байна.

post_logout_redirect_uris нь oauth2_clients дээрх шинэ багана (migration 00041). redirect_uris-ийг дахин ашиглаагүй нь санаатай: нэвтрэх callback бол код хүлээж авдаг машин уншдаг зам, гарсны дараах хаяг бол хүн харах хуудас.


Энэ өөрчлөлтгүйгээр SSO нь single sign-on болохгүй. Клиент суулгац хүнийг провайдерийн /oauth2/auth руу илгээх нь өөр сайтаас ирсэн top-level navigation бөгөөд SameSite=Strict cookie тийм хүсэлт дээр илгээгддэггүй. Тэгэхээр authorization endpoint нь минутын өмнө нэвтэрсэн хүнийг "нэвтрээгүй" гэж үзээд нэвтрэх дэлгэц үзүүлнэ.

CSRF-ийн хувьд алдагдал байхгүй: cookie хэзээ ч тэр хамгаалалт байгаагүй. Төлөв өөрчлөх бүх хүсэлт security.CSRFMiddleware-ээр орох ба тэр нь манай хуудас үүсгэсэн гэдгийн эерэг нотолгоо (зөвшөөрөгдсөн Origin, эсвэл Sec-Fetch-Site: same-origin|none) шаарддаг. Cross-site POST нь cookie-ийн бодлогоос үл хамааран тэнд татгалзана.


Хөгжүүлэлтэд хоёр суулгацыг холбох

# Провайдер: :8080 (API) / :3000 (вэб) — ердийн тохиргоо.
SSO_ISSUER=http://localhost:8080
OAUTH_REDIRECT_HOSTS=localhost

# Клиент: :8090 (API) / :3010 (вэб)
SSO_CLIENT_ISSUER=http://localhost:8080
SSO_CLIENT_ID=<провайдер дээр үүсгэсэн client_id>
SSO_CLIENT_SECRET=<байвал>
SSO_CLIENT_REDIRECT_URI=http://localhost:8090/api/v1/auth/sso/callback
SSO_CLIENT_POST_LOGOUT_REDIRECT_URI=http://localhost:3010/
SSO_CLIENT_TENANT=demo

Провайдер дээрх клиентийн redirect_urishttp://localhost:8090/api/v1/auth/sso/callback, post_logout_redirect_urishttp://localhost:3010/ гэж бүртгэнэ. Loopback хаягийг redirect шалгалт үргэлж зөвшөөрдөг.