如何使用 ADAL JS 获取当前用户角色
How to get current user roles with ADAL JS
我有以下 React 应用程序,它是来自 envato 的模板。
我已经使用这个组件集成了 Azure AD 身份验证,它工作得很好:
https://github.com/salvoravida/react-adal
但是我想创建角色,并且我希望能够根据当前用户拥有的角色在侧边栏上显示菜单项。
我已经知道如何使用应用程序清单在 Azure AD 中创建角色,所以这个问题更多是关于如何在用户通过身份验证后获取这些角色以及如何根据声明值呈现菜单项。
相关代码在这里:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
AdalConfig.js
从 'react-adal';
导入 { AuthenticationContext, adalFetch, withAdalLogin }
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);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
dashboard.js
import React, { Component } from 'react';
import LayoutContentWrapper from '../components/utility/layoutWrapper';
import LayoutContent from '../components/utility/layoutContent';
export default class extends Component {
render() {
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
<LayoutContent>
<h1>ISOMORPHIC DASHBOARD HOME</h1>
</LayoutContent>
</LayoutContentWrapper>
);
}
}
Router.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import { connect } from 'react-redux';
import App from './containers/App/App';
import asyncComponent from './helpers/AsyncFunc';
const RestrictedRoute = ({ component: Component, isLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>}
/>
);
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
render={() => <Redirect to="/dashboard" />}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};
export default connect(state => ({
isLoggedIn: state.Auth.get('idToken') !== null,
}))(PublicRoutes);
边栏
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);
}
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.indexOf(key) > -1)
);
const latestCloseKey = app.openKeys.find(
key => !(newOpenKeys.indexOf(key) > -1)
);
let nextOpenKeys = [];
if (latestOpenKey) {
nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
}
if (latestCloseKey) {
nextOpenKeys = this.getAncestorKeys(latestCloseKey);
}
changeOpenKeys(nextOpenKeys);
}
getAncestorKeys = key => {
const map = {
sub3: ["sub2"]
};
return map[key] || [];
};
getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
const { key, label, leftIcon, children } = singleOption;
const url = stripTrailingSlash(this.props.url);
if (children) {
return (
<SubMenu
key={key}
title={
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
}
>
{children.map(child => {
const linkTo = child.withoutDashboard
? `/${child.key}`
: `${url}/${child.key}`;
return (
<Menu.Item style={submenuStyle} key={child.key}>
<Link style={submenuColor} to={linkTo}>
<IntlMessages id={child.label} />
</Link>
</Menu.Item>
);
})}
</SubMenu>
);
}
return (
<Menu.Item key={key}>
<Link to={`${url}/${key}`}>
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
</Link>
</Menu.Item>
);
};
render() {
const { app, toggleOpenDrawer, height } = this.props;
const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
const { openDrawer } = app;
const mode = collapsed === true ? "vertical" : "inline";
const onMouseEnter = event => {
if (openDrawer === false) {
toggleOpenDrawer();
}
return;
};
const onMouseLeave = () => {
if (openDrawer === true) {
toggleOpenDrawer();
}
return;
};
const customizedTheme = themes[themeConfig.theme];
const styling = {
backgroundColor: customizedTheme.backgroundColor
};
const submenuStyle = {
backgroundColor: "rgba(0,0,0,0.3)",
color: customizedTheme.textColor
};
const submenuColor = {
color: customizedTheme.textColor
};
return (
<SidebarWrapper>
<Sider
trigger={null}
collapsible={true}
collapsed={collapsed}
width="240"
className="isomorphicSidebar"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={styling}
>
<Logo collapsed={collapsed} />
<Scrollbars style={{ height: height - 70 }}>
<Menu
onClick={this.handleClick}
theme="dark"
className="isoDashboardMenu"
mode={mode}
openKeys={collapsed ? [] : app.openKeys}
selectedKeys={app.current}
onOpenChange={this.onOpenChange}
>
{options.map(singleOption =>
this.getMenuItem({ submenuStyle, submenuColor, singleOption })
)}
</Menu>
</Scrollbars>
</Sider>
</SidebarWrapper>
);
}
}
export default connect(
state => ({
app: state.App.toJS(),
height: state.App.toJS().height
}),
{ toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);
是的,我知道代码太长,但我认为为问题提供良好的背景是有意义的。
来自 salvarovida 的 react-adal 打包器在底层使用了 adal js 库,所以基本上它是一个包装器。
显然可以通过这行代码获得角色,但不确定如何使用它以及在哪里使用它。
https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/713
既然您已经提到您将创建 Azure AD 角色,那么您也可以将 users/groups 添加到这些角色。
见Add app roles in azure ad apps.
将这些角色映射到用户后,您可以为用户查询图表 API。
GET https://graph.microsoft.com/v1.0/me
这将 return 响应为
HTTP/1.1 200 OK
Content-type: application/json
Content-length: 491
{
"displayName": "displayName-value",
"givenName": "givenName-value",
"mail": "mail-value",
"surname": "surname-value",
"userPrincipalName": "userPrincipalName-value",
"id": "id-value"
}
现在您可以查询 Graph API 以获取与该用户关联的角色
POST /groups/{id}/getMemberGroups
响应将包含映射到用户的所有组的列表。您可以存储在 web.config 中创建的新创建的 groups/roles 并检查它们是否是 returned 列表的一部分。
注意:您需要 Directory.Read.All、Directory.ReadWrite.All、Directory.AccessAsUser.All 的权限才能进行此调用。
来源:https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0
您可以使用 Web API 或 JS 进行这些 API 调用。我更喜欢坚持使用 WebAPI 获取角色,并在前端 API 调用(获取菜单、设置权限等)中 returning 这些角色.
此外,请参阅 https://graphexplorer.azurewebsites.net/ 以在代码中实现这些调用之前对其进行测试。
如果您想要示例应用程序,请参阅此 - Authorization in a web app using Azure AD application roles & role claims
我有以下 React 应用程序,它是来自 envato 的模板。 我已经使用这个组件集成了 Azure AD 身份验证,它工作得很好:
https://github.com/salvoravida/react-adal
但是我想创建角色,并且我希望能够根据当前用户拥有的角色在侧边栏上显示菜单项。
我已经知道如何使用应用程序清单在 Azure AD 中创建角色,所以这个问题更多是关于如何在用户通过身份验证后获取这些角色以及如何根据声明值呈现菜单项。
相关代码在这里:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
AdalConfig.js
从 'react-adal';
导入 { AuthenticationContext, adalFetch, withAdalLogin }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);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
dashboard.js
import React, { Component } from 'react';
import LayoutContentWrapper from '../components/utility/layoutWrapper';
import LayoutContent from '../components/utility/layoutContent';
export default class extends Component {
render() {
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
<LayoutContent>
<h1>ISOMORPHIC DASHBOARD HOME</h1>
</LayoutContent>
</LayoutContentWrapper>
);
}
}
Router.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import { connect } from 'react-redux';
import App from './containers/App/App';
import asyncComponent from './helpers/AsyncFunc';
const RestrictedRoute = ({ component: Component, isLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>}
/>
);
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
render={() => <Redirect to="/dashboard" />}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};
export default connect(state => ({
isLoggedIn: state.Auth.get('idToken') !== null,
}))(PublicRoutes);
边栏
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);
}
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.indexOf(key) > -1)
);
const latestCloseKey = app.openKeys.find(
key => !(newOpenKeys.indexOf(key) > -1)
);
let nextOpenKeys = [];
if (latestOpenKey) {
nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
}
if (latestCloseKey) {
nextOpenKeys = this.getAncestorKeys(latestCloseKey);
}
changeOpenKeys(nextOpenKeys);
}
getAncestorKeys = key => {
const map = {
sub3: ["sub2"]
};
return map[key] || [];
};
getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
const { key, label, leftIcon, children } = singleOption;
const url = stripTrailingSlash(this.props.url);
if (children) {
return (
<SubMenu
key={key}
title={
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
}
>
{children.map(child => {
const linkTo = child.withoutDashboard
? `/${child.key}`
: `${url}/${child.key}`;
return (
<Menu.Item style={submenuStyle} key={child.key}>
<Link style={submenuColor} to={linkTo}>
<IntlMessages id={child.label} />
</Link>
</Menu.Item>
);
})}
</SubMenu>
);
}
return (
<Menu.Item key={key}>
<Link to={`${url}/${key}`}>
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
</Link>
</Menu.Item>
);
};
render() {
const { app, toggleOpenDrawer, height } = this.props;
const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
const { openDrawer } = app;
const mode = collapsed === true ? "vertical" : "inline";
const onMouseEnter = event => {
if (openDrawer === false) {
toggleOpenDrawer();
}
return;
};
const onMouseLeave = () => {
if (openDrawer === true) {
toggleOpenDrawer();
}
return;
};
const customizedTheme = themes[themeConfig.theme];
const styling = {
backgroundColor: customizedTheme.backgroundColor
};
const submenuStyle = {
backgroundColor: "rgba(0,0,0,0.3)",
color: customizedTheme.textColor
};
const submenuColor = {
color: customizedTheme.textColor
};
return (
<SidebarWrapper>
<Sider
trigger={null}
collapsible={true}
collapsed={collapsed}
width="240"
className="isomorphicSidebar"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={styling}
>
<Logo collapsed={collapsed} />
<Scrollbars style={{ height: height - 70 }}>
<Menu
onClick={this.handleClick}
theme="dark"
className="isoDashboardMenu"
mode={mode}
openKeys={collapsed ? [] : app.openKeys}
selectedKeys={app.current}
onOpenChange={this.onOpenChange}
>
{options.map(singleOption =>
this.getMenuItem({ submenuStyle, submenuColor, singleOption })
)}
</Menu>
</Scrollbars>
</Sider>
</SidebarWrapper>
);
}
}
export default connect(
state => ({
app: state.App.toJS(),
height: state.App.toJS().height
}),
{ toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);
是的,我知道代码太长,但我认为为问题提供良好的背景是有意义的。
来自 salvarovida 的 react-adal 打包器在底层使用了 adal js 库,所以基本上它是一个包装器。
显然可以通过这行代码获得角色,但不确定如何使用它以及在哪里使用它。
https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/713
既然您已经提到您将创建 Azure AD 角色,那么您也可以将 users/groups 添加到这些角色。
见Add app roles in azure ad apps.
将这些角色映射到用户后,您可以为用户查询图表 API。
GET https://graph.microsoft.com/v1.0/me
这将 return 响应为
HTTP/1.1 200 OK
Content-type: application/json
Content-length: 491
{
"displayName": "displayName-value",
"givenName": "givenName-value",
"mail": "mail-value",
"surname": "surname-value",
"userPrincipalName": "userPrincipalName-value",
"id": "id-value"
}
现在您可以查询 Graph API 以获取与该用户关联的角色
POST /groups/{id}/getMemberGroups
响应将包含映射到用户的所有组的列表。您可以存储在 web.config 中创建的新创建的 groups/roles 并检查它们是否是 returned 列表的一部分。
注意:您需要 Directory.Read.All、Directory.ReadWrite.All、Directory.AccessAsUser.All 的权限才能进行此调用。
来源:https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0
您可以使用 Web API 或 JS 进行这些 API 调用。我更喜欢坚持使用 WebAPI 获取角色,并在前端 API 调用(获取菜单、设置权限等)中 returning 这些角色.
此外,请参阅 https://graphexplorer.azurewebsites.net/ 以在代码中实现这些调用之前对其进行测试。
如果您想要示例应用程序,请参阅此 - Authorization in a web app using Azure AD application roles & role claims