React应用中的路由权限管理
页面访问权限控制机制
- 自定义权限路由组件实现方案
- 基于官方文档构建AuthRoute权限控制组件
- 登录成功后页面跳转逻辑优化
概述
现代Web应用通常包含两类功能页面:
功能分类:
- 需要用户认证的功能(如:获取用户个人信息)
- 公开访问的功能(如:浏览房产列表)
页面分类:
- 需要登录才能访问的页面(如:用户个人中心)
- 无需登录即可访问的页面(如:应用首页)
对于需要认证的功能,我们使用axios拦截器统一处理(如:添加认证请求头)。
对于需要认证的页面,我们使用路由守卫进行访问控制。
功能级权限控制 - 使用axios拦截器统一处理认证令牌
- 在API配置文件中添加请求拦截器
- 获取当前请求的URL路径
- 判断是否为需要认证的接口(例如:以/user开头,且非登录注册接口)
- 为需要认证的接口添加Authorization请求头
// 请求拦截器配置
API.interceptors.request.use(config => {
const { url } = config
// 判断是否需要认证
if (
url.startsWith('/user') &&
!url.startsWith('/user/login') &&
!url.startsWith('/user/register')
) {
// 添加认证头
config.headers.Authorization = getAuthToken()
}
return config
})
- 添加响应拦截器处理认证失败情况
- 检测返回状态码,识别令牌过期情况
- 令牌失效时清除本地认证信息
// 响应拦截器配置
API.interceptors.response.use(response => {
const { status } = response.data
if (status === 401) {
// 令牌失效,清除认证信息
removeAuthToken()
}
return response
})
页面级权限控制 - AuthRoute权限路由组件
实现原理
React路由本身不提供直接的权限控制组件,需要我们自定义实现类似Vue路由导航守卫的功能。AuthRoute本质上是对标准Route组件的封装,增加了权限验证逻辑。
使用示例:
核心实现逻辑:
- 使用render props模式指定路由组件
- 利用Redirect组件实现未认证用户的重定向
// 官方文档中的核心实现
function ProtectedRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
// 检查认证状态
authService.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/signin",
// 保存当前路径,用于登录后跳转
state: { from: props.location }
}}
/>
)
}
/>
);
}
封装AuthRoute权限路由组件
- 在components目录下创建AuthRoute/index.js文件
- 创建并导出AuthRoute组件
- 在组件中返回Route组件并添加自定义渲染逻辑
- 在render方法中调用认证检查函数
- 根据认证状态决定渲染目标组件或重定向到登录页
- 将接收的props传递给Route组件,保持与标准Route一致的接口
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props => {
const isLoggedIn = checkAuthStatus()
if (isLoggedIn) {
// 已认证,渲染目标组件
return <Component {...props} />
} else {
// 未认证,重定向到登录页
return (
<Redirect
to={{
pathname: '/signin',
state: {
from: props.location
}
}}
/>
)
}
}}
/>
)
}
export default ProtectedRoute
优化登录成功后的跳转逻辑
- 登录成功后检查来源页面信息
- 如果没有来源页面信息,返回上一页
- 如果有来源页面信息,跳转到目标页面
- 推荐使用replace而非push进行页面跳转
// 登录表单提交处理
handleSubmit: async (values, { props }) => {
...
if (status === 200) {
// 保存认证令牌
localStorage.setItem('app_token', response.token)
/*
1. 检查是否有来源页面信息
2. 无来源信息则返回上一页
3. 有来源信息则跳转到目标页面
*/
if (!props.location.state) {
// 直接进入登录页,返回上一页
props.history.go(-1)
} else {
// 使用replace模式跳转,避免历史记录堆积
props.history.replace(props.location.state.from.pathname)
}
} else {
// 登录失败处理
Toast.error(message, 2)
}
}