Данная статья будет полезна разработчикам браузерных (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
![](https://habrastorage.org/getpro/habr/upload_files/e98/fc6/b8f/e98fc6b8fa0ad7bca28971bc4ca9e693.png)
Затем Configure OpenID Connect
![](https://habrastorage.org/getpro/habr/upload_files/046/e16/6b2/046e166b226d4cca84cc9f2f51e38a9c.png)
В открывшейся форме оставьте все настройки без изменений и нажмите кнопку Create
![](https://habrastorage.org/getpro/habr/upload_files/74a/9bd/c29/74a9bdc29c0a160c84944f9e45962273.png)
Теперь создадим OAuth2/OIDC клиент, который будет использовать SPA приложение для аутентификации.
Зайдите в консоль администратора, выберите требуемый realm, в меню слева выберите пункт Applications и далее OAuth 2.0
В таблице Agents нажмите кнопку New
![](https://habrastorage.org/getpro/habr/upload_files/71b/d80/bfe/71bd80bfe7b7c24407616f7efc527120.png)
Введите 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.
![](https://habrastorage.org/getpro/habr/upload_files/0a1/1f4/61f/0a11f461fbee9a5fdf842f668fbf94d9.png)
Далее перейдите в CORS Settings и включите поддержку CORS
![](https://habrastorage.org/getpro/habr/upload_files/947/1f8/581/9471f858193c0329867d04d2d5114200.png)
Нажмите `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/
![](https://habrastorage.org/getpro/habr/upload_files/bbc/7ca/872/bbc7ca872b36dd8c0bf9ba822422591c.png)
Нажмите кнопку Login. Вас перенаправит на аутентификацию в OpenAM. Введите логин пользователя demo
и пароль changeit
.
![](https://habrastorage.org/getpro/habr/upload_files/fd6/54b/cda/fd654bcda9593034a6dc88ead9bba7d5.png)
Подтвердите согласие на доступ к данным
![](https://habrastorage.org/getpro/habr/upload_files/201/61c/964/20161c964d9e04bae1e680d61a387aab.png)
После этого браузер перенаправит обратно в приложение и успешно аутентифицирует пользователя: Если все настроено корректно, SPA приложение отобразит сообщение об успешной аутентификации:
![](https://habrastorage.org/getpro/habr/upload_files/e64/9c7/e82/e649c7e82377fd2907bd22d25e2e10af.png)