如何通过ADAL JS获取当前用户角色并实现React侧边栏菜单权限控制
实现Azure AD角色获取与侧边栏权限控制
我来帮你一步步搞定这个需求,基于你现有的react-adal集成和Redux配置,咱们分几个核心步骤来实现:
1. 从Azure AD解析用户角色
react-adal的authContext已经缓存了用户的id token,我们可以从中提取角色信息。首先在adalConfig.js里加一个解析token的工具函数:
// adalConfig.js import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal'; // 你的原有配置 export const adalConfig = { tenant: 'abc-af96-4f7c-82db-b6f0bd7ae9b6', clientId: 'abc-969c-49b2-8a58-78eece990daf', endpoints: { api:'abc-083c-4c10-b40f-f1d764319b21', apiUrl: 'https://abc.azurewebsites.net/api' }, cacheLocation: 'localStorage' }; export const authContext = new AuthenticationContext(adalConfig); // 新增:解析id token获取用户角色 export const getUserRoles = () => { const user = authContext.getCachedUser(); if (!user) return []; // 解析token的payload部分(JWT的第二段) const tokenPayload = JSON.parse(atob(user.idToken.split('.')[1])); // Azure AD的角色默认在`roles`字段里,若用组映射角色需看`groups` return tokenPayload.roles || []; }; // 你的原有导出 export const adalApiFetch = (fetch, url, options) => adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options); export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
2. 把角色存入Redux状态
你的项目已经在用Redux管理登录状态,我们把角色也加入状态管理。首先更新Auth reducer:
// 假设你的reducer在redux/auth/reducer.js const initialState = { idToken: null, roles: [] // 新增角色数组 }; export default (state = initialState, action) => { switch (action.type) { case 'SET_ID_TOKEN': return { ...state, idToken: action.payload }; case 'SET_USER_ROLES': // 新增处理角色的action return { ...state, roles: action.payload }; default: return state; } };
然后定义对应的action:
// redux/auth/actions.js export const setUserRoles = (roles) => ({ type: 'SET_USER_ROLES', payload: roles });
3. 在认证完成后获取角色
在用户登录成功后,调用工具函数获取角色并更新Redux。比如在App.js(路由中RestrictedRoute对应的组件)的挂载阶段:
// containers/App/App.js import React, { Component } from 'react'; import { connect } from 'react-redux'; import { setUserRoles } from '../../redux/auth/actions'; import { getUserRoles } from '../../adalConfig'; class App extends Component { componentDidMount() { // 获取当前用户角色并更新Redux const userRoles = getUserRoles(); this.props.setUserRoles(userRoles); } render() { return ( // 你的原有App内容,比如包含侧边栏和路由的布局 ); } } export default connect(null, { setUserRoles })(App);
4. 根据角色过滤侧边栏菜单
接下来修改侧边栏组件,根据Redux里的角色控制菜单项显示。首先给菜单配置options.js添加权限规则:
// sidebar/options.js export default [ { key: 'dashboard', title: <IntlMessages id="sidebar.dashboard" />, icon: 'dashboard', path: '/dashboard', requiredRoles: ['Admin', 'RegularUser'] // 这两个角色都能看到 }, { key: 'admin-settings', title: <IntlMessages id="sidebar.adminSettings" />, icon: 'setting', path: '/admin', requiredRoles: ['Admin'] // 只有Admin能看到 }, { key: 'profile', title: <IntlMessages id="sidebar.profile" />, icon: 'user', path: '/profile' // 无权限要求,所有登录用户可见 } ];
然后修改Sidebar组件,添加过滤逻辑:
// sidebar/index.js import React, { Component } from "react"; import { connect } from "react-redux"; import clone from "clone"; import { Link } from "react-router-dom"; import { Layout } from "antd"; import options from "./options"; import Scrollbars from "../../components/utility/customScrollBar.js"; import Menu from "../../components/uielements/menu"; import IntlMessages from "../../components/utility/intlMessages"; import SidebarWrapper from "./sidebar.style"; import appActions from "../../redux/app/actions"; import Logo from "../../components/utility/logo"; import themes from "../../settings/themes"; import { themeConfig } from "../../settings"; const SubMenu = Menu.SubMenu; const { Sider } = Layout; const { toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed } = appActions; const stripTrailingSlash = str => { if (str.substr(-1) === "/") { return str.substr(0, str.length - 1); } return str; }; class Sidebar extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); this.onOpenChange = this.onOpenChange.bind(this); this.renderMenuItems = this.renderMenuItems.bind(this); } handleClick(e) { this.props.changeCurrent([e.key]); if (this.props.app.view === "MobileView") { setTimeout(() => { this.props.toggleCollapsed(); this.props.toggleOpenDrawer(); }, 100); } } onOpenChange(newOpenKeys) { // 你的原有逻辑 const { app, changeOpenKeys } = this.props; const latestOpenKey = newOpenKeys.find(key => !app.openKeys.includes(key)); const latestCloseKey = app.openKeys.find(key => !newOpenKeys.includes(key)); let nextOpenKeys = []; if (latestOpenKey) { nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey); } if (latestCloseKey) { nextOpenKeys = this.getAncestorKeys(latestCloseKey); } changeOpenKeys(nextOpenKeys); } // 新增:过滤并渲染菜单项 renderMenuItems() { const { roles } = this.props; // 克隆原配置避免修改源数据 const filteredOptions = clone(options).filter(option => { // 无权限要求的菜单项直接显示 if (!option.requiredRoles) return true; // 检查用户是否拥有至少一个所需角色 return option.requiredRoles.some(role => roles.includes(role)); }); return filteredOptions.map(option => { // 处理带子菜单的项 if (option.children) { // 过滤子菜单 const filteredChildren = option.children.filter(child => { if (!child.requiredRoles) return true; return child.requiredRoles.some(role => roles.includes(role)); }); // 子项全被过滤则不显示父菜单 if (filteredChildren.length === 0) return null; return ( <SubMenu key={option.key} title={ <span> {option.icon && <i className={`icon ${option.icon}`} />} <span>{option.title}</span> </span> } > {filteredChildren.map(child => ( <Menu.Item key={child.key}> <Link to={child.path}>{child.title}</Link> </Menu.Item> ))} </SubMenu> ); } // 普通菜单项 return ( <Menu.Item key={option.key}> <Link to={option.path}> {option.icon && <i className={`icon ${option.icon}`} />} <span>{option.title}</span> </Link> </Menu.Item> ); }); } render() { return ( <SidebarWrapper> <Sider> <Logo /> <Scrollbars> <Menu onClick={this.handleClick} mode="inline" openKeys={this.props.app.openKeys} selectedKeys={this.props.app.current} theme={themeConfig.theme} > {/* 调用渲染方法 */} {this.renderMenuItems()} </Menu> </Scrollbars> </Sider> </SidebarWrapper> ); } } // 连接Redux,获取用户角色 const mapStateToProps = state => ({ app: state.App, roles: state.Auth.roles // 对应Auth reducer里的roles字段 }); export default connect(mapStateToProps, { toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed })(Sidebar);
关键注意点
- 确认Azure AD应用清单里的角色已正确分配给用户/组,且id token包含
roles声明。若未显示,检查应用清单的optionalClaims,确保将roles添加到id token声明中。 - 若通过Azure AD组实现角色,id token会返回组的对象ID,需在前端维护组ID到角色名称的映射表后再进行过滤。
内容的提问来源于stack exchange,提问作者Luis Valencia
相关产品推荐
相关产品推荐

