使用 MSAL.js时,应了解检索用户令牌的含义以及如何管理这些令牌的生存期。
令牌生存期和到期时间
可以配置由Microsoft 标识平台颁发的访问、ID 或安全断言标记语言(SAML)令牌的令牌生存期。 下面汇总了一些信息。
ID 令牌
ID 令牌绑定到特定的账户和客户端组合,通常包含有关用户的资料信息。 通常,Web 应用程序的用户会话生存期将与 ID 令牌会话生存期(默认为 24 小时)匹配。 可以阅读有关 配置令牌生存期的详细信息。
访问令牌
浏览器中的访问令牌默认过期为 1 小时。 在这 1 小时之后,任何使用过期 Bearer 令牌发起的调用都将被拒绝。 可以使用通过此令牌获取的刷新令牌静默刷新此令牌。 可以阅读有关 配置令牌生存期的详细信息。
刷新令牌
提供给单页应用程序的刷新令牌是有时效限制的刷新令牌(通常自获取之时起 24 小时内有效)。 这是一个在整个使用寿命期间都不可调节、不可滑动的窗口。 每当使用刷新令牌为访问令牌续期时,都会随同续期后的访问令牌一起获取一个新的刷新令牌。 此新刷新令牌的生存期等于原始刷新令牌的剩余生存期。 刷新令牌过期后,必须启动新的授权代码流来检索授权代码并将其交易为一组新的令牌。
注意:获取新的刷新令牌时,msal.js 将缓存的刷新令牌替换为新的刷新令牌,但旧的刷新令牌不会由服务器失效,并且仍可用于获取访问令牌,直到其过期。
令牌续订
该 PublicClientApplication 对象公开一个调用 acquireTokenSilent 的 API,该 API 旨在以无提示方式检索未过期的令牌。 在几个步骤中执行此操作:
- 检查对于给定的
scopes、client id、authority和/或homeAccountIdentifier,令牌缓存中是否已存在令牌。 - 如果存在与给定参数对应的令牌,请确保只匹配到一个结果,并检查其是否过期。
- 如果访问令牌未过期,MSAL 将返回包含相关令牌的响应。
- 如果访问令牌已过期,但刷新令牌仍然有效,则 MSAL 将使用给定的刷新令牌来检索一组新的令牌,然后返回响应。
- 如果刷新令牌已过期,MSAL 将尝试使用隐藏的 iframe 以无提示方式检索访问令牌。 这将使用帐户声明对象中的 sid 或用户名来检索有关用户的会话的提示。 如果此隐藏的 iframe 调用失败,MSAL 将把来自服务器的错误作为
InteractionRequiredAuthError传递,并要求获取授权代码以获取一组新的令牌。 为此,可以使用PublicClientApplication对象执行登录操作或调用 acquireToken API。 如果会话仍然处于活动状态,服务器将发送代码,且没有任何用户提示。 否则,系统将要求用户输入其凭据。
有关可以为该方法设置的配置参数的详细信息,请参阅acquireTokenSilent一文。
避免用户会话中间的交互式中断
在某些情况下,你可能希望在用户会话开始时预先调用交互,以确保他们可以继续以无提示方式获取令牌并使用应用程序,而不会进一步中断。 当然,你可以通过每次首次加载应用程序时调用交互来实现此目的,但是,当用户已具有上一个会话或其他窗口/选项卡的令牌时,用户体验不佳,性能较低。相反,可以使用一些请求参数 acquireTokenSilent 来确保缓存具有必要的令牌,以便以无提示方式返回一些任意时间长度。
为了确保 acquireTokenSilent 可以返回有效期至少为 1 小时的有效令牌:
- 在页面加载时调用
acquireTokenSilent,并将forceRefresh请求参数设置为true。 这会跳过缓存并获取新的令牌,然后可在后续调用时从缓存中提供。 - 在后续调用中,请将
forceRefresh保持为未设置状态,或明确将其设为false,以确保令牌可以从缓存中获取
为确保 acquireTokenSilent 在最长不超过 24 小时的任意时长内都能返回有效令牌:
- 在页面加载时调用
acquireTokenSilent,并将forceRefresh请求参数设置为true,同时将refreshTokenExpirationOffsetSeconds参数设置为所需的无交互时长(以秒为单位) - 在后续调用中,将
forceRefresh和refreshTokenExpirationOffsetSeconds保持为未设置状态,以确保令牌可以从缓存中获取
例如,如果希望确保用户可以在接下来的 2 小时内以无提示方式获取令牌:
var request = {
scopes: ["Mail.Read"],
account: currentAccount,
forceRefresh: true,
refreshTokenExpirationOffsetSeconds: 7200 // 2 hours * 60 minutes * 60 seconds = 7200 seconds
};
const tokenResponse = await msalInstance.acquireTokenSilent(request).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
await msalInstance.acquireTokenRedirect(request);
}
});
注意:即使刷新令牌尚未过期,也不能保证一定能够以静默方式获取令牌。 上述模式是尽量减少不方便时交互的尝试,但不会消除所需时间范围内所需交互的可能性。 此外,并非所有身份提供程序都会返回刷新令牌的过期时间,因此在这些情况下,将不会评估 refreshTokenExpirationOffsetSeconds 请求参数。
缓存查找策略
可选择在请求中指定缓存查找策略。 缓存查找策略包括:
-
CacheLookupPolicy.Default-acquireTokenSilent将尝试从缓存中检索访问令牌。 如果访问令牌已过期或找不到,则刷新令牌将用于获取新令牌。 最后,如果刷新令牌已过期,acquireTokenSilent将尝试以无提示方式获取新的访问令牌、ID 令牌和刷新令牌。 -
CacheLookupPolicy.AccessToken-acquireTokenSilent只会在缓存中查找访问令牌。 它不会尝试续订访问或刷新令牌。 -
CacheLookupPolicy.AccessTokenAndRefreshToken-acquireTokenSilent将尝试从缓存中检索访问令牌。 如果访问令牌已过期或找不到,则刷新令牌将用于获取新令牌。 如果刷新令牌已过期,则将无法续期,且acquireTokenSilent会失败。 -
CacheLookupPolicy.RefreshToken-acquireTokenSilent不会尝试从缓存中检索访问令牌,而是尝试交换缓存的刷新令牌以获取新的访问令牌。 如果刷新令牌已过期,则无法续订,且acquireTokenSilent将失败。 -
CacheLookupPolicy.RefreshTokenAndNetwork-acquireTokenSilent不会在缓存中查找访问令牌。 它将使用已缓存的刷新令牌直接发起网络请求。 如果刷新令牌已过期,将尝试续订它。 这等效于设置forceRefresh: true。 -
CacheLookupPolicy.Skip-acquireTokenSilent将尝试续订访问令牌和刷新令牌。 它不会在缓存中查找。 如果浏览器阻止了第三方 Cookie,则始终会失败。
代码段
Popup
var username = "[email protected]";
var currentAccount = msalInstance.getAccount({ username });
var silentRequest = {
scopes: ["Mail.Read"],
account: currentAccount,
forceRefresh: false,
cacheLookupPolicy: CacheLookupPolicy.Default // will default to CacheLookupPolicy.Default if omitted
};
var request = {
scopes: ["Mail.Read"],
loginHint: currentAccount.username // For v1 endpoints, use upn from idToken claims
};
const tokenResponse = await msalInstance.acquireTokenSilent(silentRequest).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return await msalInstance.acquireTokenPopup(request).catch(error => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return msalInstance.acquireTokenRedirect(request)
}
});
}
});
重定向
var username = "[email protected]";
var currentAccount = msalInstance.getAccount({ username });
var silentRequest = {
scopes: ["Mail.Read"],
account: currentAccount,
forceRefresh: false,
cacheLookupPolicy: CacheLookupPolicy.Default // will default to CacheLookupPolicy.Default if omitted
};
var request = {
scopes: ["Mail.Read"],
loginHint: currentAccount.username // For v1 endpoints, use upn from idToken claims
};
const tokenResponse = await msalInstance.acquireTokenSilent(silentRequest).catch(error => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return msalInstance.acquireTokenRedirect(request)
}
});
后续步骤
了解如何执行 注销。