Реализация OIDC

This commit is contained in:
2025-11-13 17:30:38 +05:00
parent 6e51417ad3
commit 264af25d51
11 changed files with 258 additions and 20 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
HTTPS=true
SSL_CRT_FILE=./certs/cert.pem
SSL_KEY_FILE=./certs/key.pem

23
certs/cert.pem Normal file
View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwTCCAqmgAwIBAgIUAr54kkXIb/FCWQ1zQG+QOXtfEu0wDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCUlUxDTALBgNVBAgMBFBlcm0xDjAMBgNVBAcMBUNoYWlr
MQwwCgYDVQQKDANJYW0xDTALBgNVBAMMBEVnb3IxJTAjBgkqhkiG9w0BCQEWFmVn
b3IubXVyYXRvdkBnbWFpbC5jb20wHhcNMjUwMjI4MTY0MDA1WhcNMjYwMjI4MTY0
MDA1WjBwMQswCQYDVQQGEwJSVTENMAsGA1UECAwEUGVybTEOMAwGA1UEBwwFQ2hh
aWsxDDAKBgNVBAoMA0lhbTENMAsGA1UEAwwERWdvcjElMCMGCSqGSIb3DQEJARYW
ZWdvci5tdXJhdG92QGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAK/EdtIakt3x8/vjvDveFo6eugk3GSRZNcnDb0HBTCDOpbkCxi3NcdBZ
glr3iSEOR2pn/9PhlKO6wR3HoBDy9viAE/CkLkBE2VGYRuSIitozYOONqXbAIQJm
gLGpuSMANrx3idBLb76UnZGgG62DdtYIuH7OUgF96twIYnKrsZAO2GtZ/Vr+c8U7
7x9P1HQZxtcZzOPTcTQW+dEstSer9tjJYAmTi1gOdu6WQZKUAHMiiE5Q0tzjDhkk
Xg7X3ttsnWUIgOPYgu7gF4NCdF7JhtuLroSTBpkU/xxwUrkmlcS/TmLAy9Lsmz9Q
+UBu6XCnWRj4lAufV6SaDFqvkfQeWa8CAwEAAaNTMFEwHQYDVR0OBBYEFKfxOTgB
vdvXzHq4xBxWeYfn77NNMB8GA1UdIwQYMBaAFKfxOTgBvdvXzHq4xBxWeYfn77NN
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACvaRSOwF+ib5Vpn
2weYkIzXSDevbuf+EGlNZnxTBXtBYbhXvjK4uGCdf3uQiBkanwOTyUHFc6OtV1Io
eph11Z+dPrLmZOJ24TT2dD1C70CBROTwH/z5om6I/JPRRd9sBzIs+Dr78uSJ8mN3
NJb6E+hP5lcgqsk0N0v3Ln3bBOm5nFVfcSrNNjccUAnp+OrJMe2gU3Otz0YE2zOm
nkkf9vHE+IADTDUY5M978DYA90PF1oTAHMdvGZYWNUGi4zCbubzI40Wq2gLG5A2h
k9FAyM+4AbVxUoYxdFVeBLdCHb594KcOj1O0ujbFN3mQ96pMQJAv/2Cl8vLkqNx1
5pWa0d0=
-----END CERTIFICATE-----

28
certs/key.pem Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvxHbSGpLd8fP7
47w73haOnroJNxkkWTXJw29BwUwgzqW5AsYtzXHQWYJa94khDkdqZ//T4ZSjusEd
x6AQ8vb4gBPwpC5ARNlRmEbkiIraM2Djjal2wCECZoCxqbkjADa8d4nQS2++lJ2R
oButg3bWCLh+zlIBfercCGJyq7GQDthrWf1a/nPFO+8fT9R0GcbXGczj03E0FvnR
LLUnq/bYyWAJk4tYDnbulkGSlABzIohOUNLc4w4ZJF4O197bbJ1lCIDj2ILu4BeD
QnReyYbbi66EkwaZFP8ccFK5JpXEv05iwMvS7Js/UPlAbulwp1kY+JQLn1ekmgxa
r5H0HlmvAgMBAAECggEAQltjmHKb29rKh+A0Yk24KmPWTEBW55gebGuyBxsYyJH7
kttvQj97pnMEeZ9WT/p6D7vvo2hYm2+YFMwWrA9uGecQoBr7sxvLB7j7mq/J7BLV
k1MaFVD3pVZZY7l1wbcE8yYWC2NPbp3g1uehS2KEbM2iCY2O/C0zi5pGwI/9Wyr/
ZlHfE/6b7ttrjOqVxXSnR2MN4VZI+0ixpG2ojqQmaWTYyArFmHb8CDl1LIa2mfJQ
m628GGYdHFKqsKOtnjpAyer0OTGJNRL9mjN/Y0jlsD3zTAOR/j48A9c6FmY1ktam
m0lX4/ZcZnEjrEObODLIxaaizDTryOTXrqVBJ0uREQKBgQDZaRsd8W/+eMAuTJEM
qVAduxbiw87T8a6NXEcAiEF5vhxqpXGh+FR4ufbkr41wHL21IYVGQQU+M3VcgGp2
bh8CyGk1olrl2VIExjcA2QR8dJ/YmxzKb2Ja6ZRMuG3VufZYxqtbcbJniap2GueC
C8iykeGTaz7MOUXJm9q6JFVHHQKBgQDO9yQEyUnBn0OrYZdqB3pAdjDCJB0Xav3H
uWu/i7qlBtveeWOt8y7NkC0UG5URlSXejncUmwQPEfMOhIg0Er/c/rZdpp9hoBNa
eH8MusNQIKZPXOGtBwikIsSJeQ04t1NXzSDEUe6lZER3DAq7BMocyXCEpITMwC/a
0+NEvKLuOwKBgQC9md4eNOqYoDHprrhotFe8Neb1iBId4A18FleNbUa8p0Ec+H+q
42i3iGZ6dWcBuO7wwfT6mcW6wyG8s/kko1DEGoc2UQq4nNfcdgiN4rT43LRyMIPh
P1YlNsMwTT7sPytJrKjQLM2LYhGYwknXrfMvV+3DpKm1bNUhx5vu7bS5OQKBgAMe
vrBDyJTercp7oii7DCDEp1+F49pihojoRrOQi7PJMq9b7SDGNcJrlgJjmA+3y+Zb
B3iMDbeccamaXeNLFRFj1aP7yxNRsnj+sAulFSS0GU3A/LX7ESpIS+Y2qPhd6ye7
s+7BvXNI269fwxmmrNVaRBP71vSvQQlvgFGc9mfZAoGAAzV+Qs2SUT8FtoBOUbMD
APCMrraaNQtPKpTq/KCKHK5FsU09RMK7EQC7XcdqKMZqOeLFyBRqVEc8WOqbdmyy
A9Ed7EVKpmDRhbr1ApmH0AQW38lYFguDluf5cWQLhrAntTmSAJdkJJgWw/EKR5zY
hmuxletwhzNRsV8CrJjNrLc=
-----END PRIVATE KEY-----

82
package-lock.json generated
View File

@ -12,8 +12,10 @@
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"oidc-client-ts": "^3.1.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-router-dom": "^7.2.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
} }
@ -3418,6 +3420,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "8.56.12", "version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@ -10207,6 +10214,14 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -10900,6 +10915,17 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
}, },
"node_modules/oidc-client-ts": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz",
"integrity": "sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==",
"dependencies": {
"jwt-decode": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -12883,6 +12909,52 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz",
"integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz",
"integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==",
"dependencies": {
"react-router": "7.2.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"engines": {
"node": ">=18"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@ -13728,6 +13800,11 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@ -14992,6 +15069,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -7,8 +7,10 @@
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"oidc-client-ts": "^3.1.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-router-dom": "^7.2.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },

View File

@ -1,25 +1,22 @@
import logo from './logo.svg'; // src/App.js
import './App.css'; import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from './components/Login';
import Logout from './components/Logout';
import Callback from './components/Callback';
import Home from './components/Home';
function App() { const App = () => {
return ( return (
<div className="App"> <Router>
<header className="App-header"> <Routes>
<img src={logo} className="App-logo" alt="logo" /> <Route path="/" element={<Home />} />
<p> <Route path="/login" element={<Login />} />
Edit <code>src/App.js</code> and save to reload. <Route path="/logout" element={<Logout />} />
</p> <Route path="/callback" element={<Callback />} />
<a </Routes>
className="App-link" </Router>
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
); );
} };
export default App; export default App;

View File

@ -0,0 +1,20 @@
// src/Callback.js
import React, { useEffect } from 'react';
import { userManager } from '../config/authConfig';
import { useNavigate } from 'react-router-dom';
const Callback = () => {
const navigate = useNavigate();
useEffect(() => {
userManager.signinRedirectCallback().then(() => {
navigate('/'); // Перенаправление на главную страницу после успешной аутентификации
}).catch((err) => {
console.error(err);
});
}, [navigate]);
return <div>Loading...</div>;
};
export default Callback;

31
src/components/Home.js Normal file
View File

@ -0,0 +1,31 @@
// src/Home.js
import React, { useEffect, useState } from 'react';
import { userManager } from '../config/authConfig';
const Home = () => {
const [user, setUser] = useState(null);
useEffect(() => {
userManager.getUser().then((user) => {
if (user) {
setUser(user);
}
});
}, []);
if (!user) {
return <div>Please <a href="/login">login</a> to continue.</div>;
}
return (
<div>
<h1>Welcome, {user.profile.name}</h1>
<p>Email: {user.profile.email}</p>
<h2>Token Information</h2>
<pre>{JSON.stringify(user, null, 2)}</pre>
<a href="/logout">Logout</a>
</div>
);
};
export default Home;

18
src/components/Login.js Normal file
View File

@ -0,0 +1,18 @@
// src/Login.js
import React from 'react';
import { userManager } from '../config/authConfig';
const Login = () => {
const handleLogin = () => {
userManager.signinRedirect();
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default Login;

18
src/components/Logout.js Normal file
View File

@ -0,0 +1,18 @@
// src/Logout.js
import React from 'react';
import { userManager } from '../config/authConfig';
const Logout = () => {
const handleLogout = () => {
userManager.signoutRedirect();
};
return (
<div>
<h1>Logout</h1>
<button onClick={handleLogout}>Logout</button>
</div>
);
};
export default Logout;

16
src/config/authConfig.js Normal file
View File

@ -0,0 +1,16 @@
// src/authConfig.js
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
const config = {
authority: 'https://localhost:5001/', // Замените на URL вашего OIDC провайдера
client_id: 'test-web-oidc', // Замените на ваш Client ID
client_secret: 'test-web-oidc',
redirect_uri: 'https://localhost:3000/callback', // URL для перенаправления после аутентификации
response_type: 'code',
scope: 'openid profile email', // Запрошенные scope
post_logout_redirect_uri: 'https://localhost:3000/', // URL для перенаправления после выхода
userStore: new WebStorageStateStore({ store: window.localStorage }), // Хранение состояния в localStorage
loadUserInfo: true, // Загружать ли информацию о пользователе
};
export const userManager = new UserManager(config);