Данная статья будет полезна разработчикам браузерных (SPA) приложений, которые хотят настроить аутентификацию пользователей. Для аутентификации будет использоваться OAuth2/OIDC протокол c PKCE. В качестве сервера аутентификации будет использоваться сервер управления аутентификации с открытым исходным кодом OpenAM.
Настройка OpenAM
Установка OpenAM
Пусть OpenAM располагается на хосте openam.example.org
. Если у вас уже установлен OpenAM, можете пропустить этот шаг. Самым простым способом развернуть OpenAM можно в Docker контейнере. Перед запуском, добавьте имя хоста и IP адрес в файл hosts
, например 127.0.0.1 openam.example.org
.
В Windows системах файл hosts
находится по адресу C:\Windows\System32\drivers\etc\hosts
, в Linux и Mac находится по адресу /etc/hosts
После этого запустите Docker контейнер OpenAM Выполните следующую команду:
docker run -h openam.example.org -p 8080:8080 --name openam openidentityplatform/openam
После того, как сервер запустится, запустите начальную конфигурацию OpenAM. Выполните следующую команду:
docker exec -w '/usr/openam/ssoconfiguratortools' openam bash -c \ 'echo "ACCEPT_LICENSES=true SERVER_URL=http://openam.example.org:8080 DEPLOYMENT_URI=/$OPENAM_PATH BASE_DIR=$OPENAM_DATA_DIR locale=en_US PLATFORM_LOCALE=en_US AM_ENC_KEY= ADMIN_PWD=passw0rd AMLDAPUSERPASSWD=p@passw0rd COOKIE_DOMAIN=openam.example.org ACCEPT_LICENSES=true DATA_STORE=embedded DIRECTORY_SSL=SIMPLE DIRECTORY_SERVER=openam.example.org DIRECTORY_PORT=50389 DIRECTORY_ADMIN_PORT=4444 DIRECTORY_JMX_PORT=1689 ROOT_SUFFIX=dc=openam,dc=example,dc=org DS_DIRMGRDN=cn=Directory Manager DS_DIRMGRPASSWD=passw0rd" > conf.file && java -jar openam-configurator-tool*.jar --file conf.file'
После успешной конфигурации OpenAM можно приступить к дальнейшей настройке.
Настройка OAuth2/OIDC провайдера
Зайдите в консоль администратора по ссылке
http://openam.example.org:8080/openam/XUI/#login/
В поле логин введите значение amadmin
, поле пароль введите значение из параметра ADMIN_PWD
команды установки, в данном случае passw0rd
Настройка OAuth2/OIDC
Выберите требуемый realm. В разделе Dashboard кликните на элементе Configure OAuth Provider
Затем Configure OpenID Connect
В открывшейся форме оставьте все настройки без изменений и нажмите кнопку Create
Теперь создадим OAuth2/OIDC клиент, который будет использовать SPA приложение для аутентификации.
Зайдите в консоль администратора, выберите требуемый realm, в меню слева выберите пункт Applications и далее OAuth 2.0
В таблице Agents нажмите кнопку New
-
Введите Name (client_id)
test_client_id
и Password (client_secret)changeit
нового приложения -
Откройте настройки приложения
-
Установите Client type в Public
-
Добавьте в список Redirection URIs URI вашего SPA приложения. В нашем случае это будет http://localhost:5173/
-
В список scope добавьте значение
openid
, это нужно, чтобы сразу получить идентификатор пользователя из возвращаемого объектаid_token
. -
Token Endpoint Authentication Method установите
client_secret_post
Настройка CORS
SPA для получения access_token и id_token выполняет кросс-доменные запросы. Для того, чтобы данные запросы не блокировал браузер, нужно включить поддержку CORS в OpenAM.
Откройте консоль администратора. В верхнем меню выберите пункт Configure → Global Services.
Далее перейдите в CORS Settings и включите поддержку CORS
Нажмите `Save Changes`
Пример SPA приложения на React
В качестве примера будем использовать приложение, написанное на React. Для упрощения не будем проверять валидность параметра state, корректность подписи возвращаемого id_token и т.д. В продуктивном окружении настоятельно рекомендуем это сделать.
Создайте нового приложение, выполнив в консоле команду:
npm create vite@latest react-openam-example -- --template react
Добавьте в зависимости библиотеку CryptoJS. Она будет нужна для генерации code_challenge.
cd react-openam-example npm install crypto-js
Замените содержимое файла react-openam-example/src/App.jsx следующим кодом:
import { useEffect, useState } from 'react' import CryptoJS from 'crypto-js'; import './App.css' const OPENAM_URL = "http://openam.example.org:8080/openam"; const OAUTH2_ENDPOINT = OPENAM_URL + "/oauth2"; const OAUTH2_AUTHORIZE_ENDPOINT = OAUTH2_ENDPOINT + "/authorize"; const OAUTH2_TOKEN_ENDPOINT = OAUTH2_ENDPOINT + "/access_token"; const CLIENT_ID = "test_client"; const SCOPE = "openid"; function App() { const [user, setUser] = useState(""); //TODO should be randomly generated, saved and then restored in production evironment const codeVerifier = "a116cb8c-5a1e-4918-a164-255ae3d8f1b1"; useEffect(() => { const params = new URLSearchParams(window.location.search) const code = params.get('code') if(!code) { return; } getToken(code) }, []) const getToken = async (code) => { const resp = await fetch(OAUTH2_TOKEN_ENDPOINT, { method: "POST", mode: "cors", cache: "no-cache", credentials: "include", headers: {'content-type': 'application/x-www-form-urlencoded'}, redirect: "follow", referrerPolicy: "no-referrer", body: new URLSearchParams({ grant_type: 'authorization_code', client_id: CLIENT_ID, code_verifier: codeVerifier, code: code, redirect_uri: window.location.origin }), }); if(resp.ok) { const accessToken = await resp.json() //TODO verify id_token signature const idToken = accessToken['id_token']; const parts = idToken.split('.') const payload = parts[1]; const jsonPayload = JSON.parse(atob(payload)); const sub = jsonPayload["sub"] setUser(sub) console.log(sub, "authenticated") } else { console.log(resp.status) } } const authOpenAM = () => { const state = "state"; const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(CryptoJS.enc.Base64url); console.log(codeChallenge); const queryString = "?redirect_uri=" + encodeURIComponent(window.location.origin) + "&client_id=" + CLIENT_ID + "&response_type=code" + "&state=" + state + "&scope=" + encodeURIComponent(SCOPE) + "&code_challenge=" + codeChallenge + "&code_challenge_method=S256"; window.location = OAUTH2_AUTHORIZE_ENDPOINT + queryString; } const getComponent = () => { if (!user) { return <> <div> <h1>Not authenticated</h1> </div> <button onClick={authOpenAM}>Login</button> </> } else { return <h1>User {user} authenticated</h1> } } return getComponent() } export default App
Проверка решения
Запустите SPA командой
npm run dev
Откройте приложение в браузере перейдя по URL http://localhost:5173/
Нажмите кнопку Login. Вас перенаправит на аутентификацию в OpenAM. Введите логин пользователя demo
и пароль changeit
.
Подтвердите согласие на доступ к данным
После этого браузер перенаправит обратно в приложение и успешно аутентифицирует пользователя: Если все настроено корректно, SPA приложение отобразит сообщение об успешной аутентификации:
ссылка на оригинал статьи https://habr.com/ru/articles/796511/
Добавить комментарий