你已构建了客户端应用程序对象。 现在,你使用它获取令牌来调用 Web API。 在 ASP.NET 或 ASP.NET Core 中,调用 Web API 是在控制器中完成的。
Microsoft.Identity.Web 添加了扩展方法,这些方法为调用 Microsoft Graph 或下游 Web API 提供便利服务。 若要详细了解这些方法,请参阅调用 Web API 的 Web 应用:调用 API。 使用这些帮助程序方法,你无需手动获取令牌。
但是,如果你确实想要手动获取令牌,可以通过以下代码以示例方式了解如何使用 Microsoft.Identity.Web 在主控制器中执行此操作。 它使用 REST API(而不是 Microsoft Graph SDK)调用 Microsoft Graph。 通常,无需获取令牌,而是需要生成添加到请求的授权标头。 若要获取授权标头,请通过依赖项注入将 IAuthorizationHeaderProvider 服务注入的构造函数控制器(如果使用 Blazor,则注入页面构造函数),并在控制器操作中使用它。 此接口提供了方法来生成包含协议(持有者、Pop 等)和一个令牌的字符串。 若要获取授权标头来代表用户调用 API,请使用 (CreateAuthorizationHeaderForUserAsync)。 若要获取授权标头来代表应用程序本身调用下游 API,请在守护程序方案中使用 (CreateAuthorizationHeaderForAppAsync)。
控制器方法受 [Authorize] 属性的保护,该属性确保只有经过身份验证的用户可使用 Web 应用。
[Authorize]
public class HomeController : Controller
{
readonly IAuthorizationHeaderProvider authorizationHeaderProvider;
public HomeController(IAuthorizationHeaderProvider authorizationHeaderProvider)
{
this.authorizationHeaderProvider = authorizationHeaderProvider;
}
// Code for the controller actions (see code below)
}
ASP.NET Core 通过依赖项注入提供 IAuthorizationHeaderProvider。
下面是 HomeController 的操作的简化代码,该操作获取令牌来调用 Microsoft Graph:
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Acquire the access token.
string[] scopes = new string[]{"user.read"};
string accessToken = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", accessToken);
string json = await client.GetStringAsync(url);
}
若要更好地了解此方案所需的代码,请参阅 ms-identity-aspnetcore-webapp-tutorial 教程的阶段 2(2-1-Web 应用调用 Microsoft Graph)步骤。
在控制器操作顶部(如果你使用 Razor 模板,则为 Razor 页面顶部)的 AuthorizeForScopes 属性由 Microsoft.Identity.Web 提供。 它确保在需要时以增量方式要求用户提供许可。
还有其他复杂的变化形式,例如:
3-WebApp-multi-APIs 教程的第 3 章中涵盖了这些高级步骤。
适用于 ASP.NET 的代码类似于为 ASP.NET Core 显示的代码:
- 受
[Authorize] 属性保护的控制器操作提取控制器的 ClaimsPrincipal 成员的租户 ID 和用户ID(ASP.NET 使用 HttpContext.User)。 这可确保只有经过身份验证的用户才能使用该应用。
Microsoft.Identity.Web 将扩展方法添加到控制器,从而提供便捷的服务来调用 Microsoft Graph 或下游 Web API,或者获取授权标头甚至令牌。 若要详细了解用于直接调用 API 的方法,请参阅调用 Web API 的 Web 应用:调用 API。 使用这些帮助程序方法,你无需手动获取令牌。
但是,如果你确实想要手动获取令牌或生成授权标头,可查看以下代码了解如何使用 Microsoft.Identity.Web 在控制器中执行此操作。 它使用 REST API 而不是 Microsoft Graph SDK 来调用 API (Microsoft Graph)。
若要获取授权标头,请使用扩展方法 IAuthorizationHeaderProvider 从控制器获取 GetAuthorizationHeaderProvider 服务。 若要获取授权标头来代表用户调用 API,请使用 CreateAuthorizationHeaderForUserAsync。 若要获取授权标头来代表应用程序本身调用下游 API,请在守护程序方案中使用 CreateAuthorizationHeaderForAppAsync。
下面的代码片段显示了 HomeController 的操作,该操作获取一个授权标头来将 Microsoft Graph 作为 REST API 进行调用:
[Authorize]
public class HomeController : Controller
{
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Get an authorization header.
IAuthorizationHeaderProvider authorizationHeaderProvider = this.GetAuthorizationHeaderProvider();
string[] scopes = new string[]{"user.read"};
string authorizationHeader = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
string json = await client.GetStringAsync(url);
}
}
下面的代码片段显示了 HomeController 的操作,该操作获取一个访问令牌来将 Microsoft Graph 作为 REST API 进行调用:
[Authorize]
public class HomeController : Controller
{
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Get an authorization header.
ITokenAcquirer tokenAcquirer = TokenAcquirerFactory.GetDefaultInstance().GetTokenAcquirer();
string[] scopes = new string[]{"user.read"};
string token = await tokenAcquirer.GetTokenForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
string json = await client.GetStringAsync(url);
}
}
在 Java 示例中,调用 API 的代码位于 getUsersFromGraph 中的 方法中。
该方法尝试调用 getAuthResultBySilentFlow。 如果用户需要许可更多作用域,则该代码会处理 MsalInteractionRequiredException 对象来质询用户。
@RequestMapping("/msal4jsample/graph/me")
public ModelAndView getUserFromGraph(HttpServletRequest httpRequest, HttpServletResponse response)
throws Throwable {
IAuthenticationResult result;
ModelAndView mav;
try {
result = authHelper.getAuthResultBySilentFlow(httpRequest, response);
} catch (ExecutionException e) {
if (e.getCause() instanceof MsalInteractionRequiredException) {
// If the silent call returns MsalInteractionRequired, redirect to authorization endpoint
// so user can consent to new scopes.
String state = UUID.randomUUID().toString();
String nonce = UUID.randomUUID().toString();
SessionManagementHelper.storeStateAndNonceInSession(httpRequest.getSession(), state, nonce);
String authorizationCodeUrl = authHelper.getAuthorizationCodeUrl(
httpRequest.getParameter("claims"),
"User.Read",
authHelper.getRedirectUriGraph(),
state,
nonce);
return new ModelAndView("redirect:" + authorizationCodeUrl);
} else {
mav = new ModelAndView("error");
mav.addObject("error", e);
return mav;
}
}
if (result == null) {
mav = new ModelAndView("error");
mav.addObject("error", new Exception("AuthenticationResult not found in session."));
} else {
mav = new ModelAndView("auth_page");
setAccountInfo(mav, httpRequest);
try {
mav.addObject("userInfo", getUserInfoFromGraph(result.accessToken()));
return mav;
} catch (Exception e) {
mav = new ModelAndView("error");
mav.addObject("error", e);
}
}
return mav;
}
// Code omitted here
在 Node.js 示例中,获取令牌的代码位于 acquireToken 类的方法 AuthProvider 中。
acquireToken(options = {}) {
return async (req, res, next) => {
try {
const msalInstance = this.getMsalInstance(this.msalConfig);
/**
* If a token cache exists in the session, deserialize it and set it as the
* cache for the new MSAL CCA instance. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
if (req.session.tokenCache) {
msalInstance.getTokenCache().deserialize(req.session.tokenCache);
}
const tokenResponse = await msalInstance.acquireTokenSilent({
account: req.session.account,
scopes: options.scopes || [],
});
/**
* On successful token acquisition, write the updated token
* cache back to the session. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
req.session.tokenCache = msalInstance.getTokenCache().serialize();
req.session.accessToken = tokenResponse.accessToken;
req.session.idToken = tokenResponse.idToken;
req.session.account = tokenResponse.account;
res.redirect(options.successRedirect);
} catch (error) {
if (error instanceof msal.InteractionRequiredAuthError) {
return this.login({
scopes: options.scopes || [],
redirectUri: options.redirectUri,
successRedirect: options.successRedirect || '/',
})(req, res, next);
}
next(error);
}
};
}
然后,此访问令牌用于处理对 /profile 终结点的请求:
router.get('/profile',
isAuthenticated, // check if user is authenticated
async function (req, res, next) {
try {
const graphResponse = await fetch(GRAPH_ME_ENDPOINT, req.session.accessToken);
res.render('profile', { profile: graphResponse });
} catch (error) {
next(error);
}
}
);
在 Python 示例中,调用 API 的代码位于 app.py 中。
该代码将尝试从令牌缓存中获取令牌。 如果无法获取令牌,则会将用户重定向到登录路由。 否则,它可以继续调用 API。
@app.route("/call_downstream_api")
def call_downstream_api():
token = auth.get_token_for_user(app_config.SCOPE)
if "error" in token:
return redirect(url_for("login"))
# Use access token to call downstream api
api_result = requests.get(
app_config.ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']},
timeout=30,
).json()
return render_template('display.html', result=api_result)