你已构建了客户端应用程序对象。 现在,你使用它获取令牌来调用 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)。
若要获取授权标头,请使用扩展方法 GetAuthorizationHeaderProvider
从控制器获取 IAuthorizationHeaderProvider
服务。 若要获取授权标头来代表用户调用 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 的代码位于 AuthPageController.java#L62 中的 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 示例中,获取令牌的代码位于 AuthProvider
类的方法 acquireToken
中。
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)