我目前正在致力于使用 Authority 服务器实现基本 React 模板的身份验证。虽然我对 React 有点生疏,但我相信我已经成功地将所有必要的组件集成到我的客户端应用程序中。
当我启动应用程序时,会出现登录页面。输入我的凭据并单击“登录”按钮后,我将被重定向到主页。然而,它开始无休止地刷新,我唯一注意到的是 URL 参数中的代码不断变化。
首先,我为我的应用程序组件实现了一个包装器来管理身份验证:
// index.js
...
<AuthWrapper>
<App />
</AuthWrapper>
...
这是 AuthWrapper 的定义:
import { useNavigate } from 'react-router-dom';
import { AuthProvider } from './AuthContext';
import PropTypes from 'prop-types';
const AuthWrapper = ({ children }) => {
const navigate = useNavigate();
return (
<AuthProvider navigate={navigate}>
{children}
</AuthProvider>
);
};
export default AuthWrapper;
我还定义了 AuthProvider(目前,我已经对代码验证器进行了硬编码;我需要弄清楚如何将其移动到应用程序的上下文):
import React, { createContext, useContext, useState } from "react";
import {generateCodeChallenge} from "utils/authutils";
import PropTypes from 'prop-types';
const AuthContext = createContext();
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children, navigate }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const codeChallenge = generateCodeChallenge('dce35c1f-194d-48c4-bd90-6f14e9042023');
const login = () => {
window.location.href = "https://localhost/Identity/authorize" +
"?client_id=web_client" +
"&redirect_uri=http://localhost:3000/authentication/callback" +
"&response_type=code" +
"&scope=web_scope offline_access" +
"&state=123" +
`&code_challenge=${codeChallenge}` +
"&code_challenge_method=S256";
};
const handleLoginCallback = () => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
setIsAuthenticated(true);
navigate("/dashboard");
};
return (
<AuthContext.Provider value={{ isAuthenticated, login, handleLoginCallback }}>
{children}
</AuthContext.Provider>
);
};
其次,我创建了一个回调页面,这是我在权限服务器中配置的页面:
import React, { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { useNavigate } from 'react-router-dom';
import { exchangeAuthorizationCode } from "services/api";
const CallbackPage = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const authorizationCode = searchParams.get("code");
if (authorizationCode) {
exchangeAuthorizationCode(authorizationCode).then((accessToken) => {
localStorage.setItem("accessToken", accessToken);
navigate("/dashboard");
});
} else {
navigate("/error");
}
}, [location.search, navigate]);
return (
<div>
<p>Redirecting...</p>
</div>
);
};
export default CallbackPage;
这是 api.js 的定义(注意 code_verifier 是相同的):
export const exchangeAuthorizationCode = async (authorizationCode, codeVerifier) => {
const requestBody = {
grant_type: "authorization_code",
code: authorizationCode,
redirect_uri: "http://localhost:3000/authentication/callback",
code_verifier: 'dce35c1f-194d-48c4-bd90-6f14e9042023',
client_id: "web_client"
};
try {
const response = await fetch("https://localhost/Identity/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error("Failed to exchange authorization code for access token");
}
const data = await response.json();
return data.access_token;
} catch (error) {
console.error("Error exchanging authorization code:", error.message);
throw error;
}
};
最后,在我的应用程序页面中,我添加了一个用于重定向的部分:
...
import { useAuth } from "AuthContext";
export default function App() {
...
const { isAuthenticated, login } = useAuth();
const { pathname } = useLocation();
useEffect(() => {
if (!isAuthenticated && pathname !== "/login") {
login(); // method to redirect to the authority server
}
}, [isAuthenticated, pathname, login]);
...
const getRoutes = (allRoutes) =>
allRoutes.map((route) => {
if (route.collapse) {
return getRoutes(route.collapse);
}
if (route.route) {
return <Route exact path={route.route} element={route.component} key={route.key} />;
}
return null;
});
...
return (
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
<Routes>
{getRoutes(routes)}
<Route path="*" element={<Navigate to="/dashboard" />} />
</Routes>
</ThemeProvider>
);
}
完整代码可以在这里
找到我遇到了一些问题,但解决问题的是这一篇:
useEffect(() => {
// Redirect to login page if not authenticated
if (!isAuthenticated && pathname != "/authentication/callback") {
login();
}
}, [isAuthenticated, login, pathname]);
之前,我是针对“登录”页面进行验证,但该页面在 React 应用程序中不存在;它是权威服务器的一部分。所以,我需要做的就是用回调页面替换路径。