验证 Azure 广告访问令牌时签名无效,但 id 令牌有效

Posted

技术标签:

【中文标题】验证 Azure 广告访问令牌时签名无效,但 id 令牌有效【英文标题】:Invalid signature while validating Azure ad access token, but id token works 【发布时间】:2018-01-01 04:44:43 【问题描述】:

我在使用 jwt.io 验证我的 azure 广告访问令牌时收到无效签名。但是,我的 id 令牌验证得很好!

我已经看到并尝试了Invalid signature while validating Azure ad access token中建议的解决方案 和https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx 但两者都不适用于我的访问令牌。

访问和 Id 令牌是通过 Adal.js 生成的:

    var endpoints = 
        "https://graph.windows.net": "https://graph.windows.net"
    ;
    var configOptions = 
        tenant: "<ad>.onmicrosoft.com", // Optional by default, it sends common
        clientId: "<app ID from azure portal>",
        postLogoutRedirectUri: window.location.origin,
        endpoints: endpoints,
    
    window.authContext = new AuthenticationContext(configOptions);

为什么我可以验证我的 ID 令牌,但不能验证我的访问令牌?

【问题讨论】:

访问令牌是用句号(.)分隔的三部分吗? 请解码访问令牌,您在 HEADER 中看到过 nonce 吗? @RasmusW 两个令牌的格式都正确,我对每个令牌中的信息进行解码都没有问题。只有验证是个问题。 @NanYu 是的,随机数在标题中。 typ、alg、x5t 和 Kid 也是如此 看我的回复,nonce导致验证失败。 【参考方案1】:

感谢Nan Yu,我设法获得了可以由任何公共jwt验证器验证的令牌,例如jwt.io (无法将我的评论放在 Nan Yu 答案下的 cmets 部分,因为它太长了)。

据我所知,Nan Yu 提到的discussion 中提到的一点是,默认情况下 Azure AD 为 Microsoft Graph 生成令牌,这些令牌使用特殊的签名机制,因此无法使用公共验证器验证签名(除了jwt.ms微软的验证器,很可能知道神秘的特殊处理是什么意思:))。

要获得可使用公共验证器验证的不用于 Microsoft Graph 的访问令牌,我必须:

删除任何与 Microsoft Graph 相关的范围(默认情况下,我只配置了一个范围 User.Read,因此在 appConfig > API 权限中将其删除) 为您的应用程序创建自定义范围(appConfig > 公开 API > 添加范围 ...)此范围将类似于 api://application-id/scope-name 在应用程序 API 权限中添加刚刚创建的范围(appConfig > API 权限 > 添加 api 权限 > 我的 API > 选择您的应用程序 > 委托权限 > 检查您的范围 > 添加权限) 然后在您的 openid 客户端范围中使用此范围,在我的情况下,我有:openid offline_access application-id/scope-name

请注意,在 openid 客户端配置中,新创建的范围使用不带 api:// 前缀(offline_access 我必须启用 refresh_token 如果不使用刷新令牌机制可以忽略)


【讨论】:

【参考方案2】:

感谢@Antoine 我修复了我的代码。在这里,我将让我个人的适用于其他所有人的 vue.js 插件参考:

import  PublicClientApplication  from '@azure/msal-browser'
import  Notify  from 'quasar'

export class MsalService 
  _msal = null
  _store = null
  _loginRequest = null

  constructor (appConfig, store) 
    this._store = store
    this._msal = new PublicClientApplication(
      
        auth: 
          clientId: appConfig.auth.clientId,
          authority: appConfig.auth.authority
        ,
        cache: 
          cacheLocation: 'localStorage'
        
      )

    this._loginRequest = 
      scopes: [`$appConfig.auth.clientId/.default`]
    
  

  async handleResponse (response) 
    await this._store.dispatch('auth/setResponse', response)
    const accounts = this._msal.getAllAccounts()
    await this._store.dispatch('auth/setAccounts', accounts)

    if (accounts.length > 0) 
      this._msal.setActiveAccount(accounts[0])
      this._msal.acquireTokenSilent(this._loginRequest).then(async (accessTokenResponse) => 
        // Acquire token silent success
        // Call API with token
        // let accessToken = accessTokenResponse.accessToken;
        await this._store.dispatch('auth/setResponse', accessTokenResponse)
      ).catch((error) => 
        Notify.create(
          message: JSON.stringify(error),
          color: 'red'
        )
        // Acquire token silent failure, and send an interactive request
        if (error.errorMessage.indexOf('interaction_required') !== -1) 
          this._msal.acquireTokenPopup(this._loginRequest).then(async (accessTokenResponse) => 
            // Acquire token interactive success
            await this._store.dispatch('auth/setResponse', accessTokenResponse)
          ).catch((error) => 
            // Acquire token interactive failure
            Notify.create(
              message: JSON.stringify(error),
              color: 'red'
            )
          )
        
      )
    
  

  async login () 
    // this._msal.handleRedirectPromise().then((res) => this.handleResponse(res))
    // await this._msal.loginRedirect(this._loginRequest)
    await this._msal.loginPopup(this._loginRequest).then((resp) => this.handleResponse(resp))
  

  async logout () 
    await this._store.dispatch('auth/setAccounts', [])
    await this._msal.logout()
  


// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/boot-files
export default (
  app,
  store,
  Vue
) => 
  const msalInstance = new MsalService(
    app.appConfig, store
  )
  Vue.prototype.$msal = msalInstance
  app.msal = msalInstance

PD:使用 quasar 框架

【讨论】:

前端通常不应该验证令牌。它甚至不应该看它。因为知道它是一个有效的令牌,你会怎么做?您可能希望从 API 请求数据,并且 API 本身应该进行验证,否则,是什么阻止了某人调用您的 API 并声称令牌有效?文档中也有说明。 docs.microsoft.com/en-us/azure/active-directory/develop/…【参考方案3】:

如果其他人有无效签名错误,您应该检查此评论:https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/521#issuecomment-577400515

解决了我的配置问题。

本质上,如果您要获取访问令牌来访问您自己的资源服务器而不是 Graph API,则您的范围参数应该是 [CLIENT_ID]/.default(如果您使用访问令牌来访问 Graph API,则不需要需要自己验证令牌)

【讨论】:

真正的救生员。由于有一个随机数,我多次重做了我的应用程序,但它不会验证【参考方案4】:

请参考帖子:https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609

但是如果查看 Jwt.Header 你会看到一个“nonce”。这意味着您需要特殊处理。正常处理会失败。

因此,如果访问令牌中包含随机数,使用 JWT.io 或 JwtSecurityToken 验证签名将不会成功。

【讨论】:

谢谢,那我就没有办法验证访问令牌了吗? 请提供令牌请求(删除租户和客户端ID等敏感信息),您可以使用提琴手或浏览器开发工具来捕获请求,我会检查为什么标头中包含nonce。此外,您无需验证 aad graph api 的访问令牌的签名。当使用 azure 广告访问令牌发送 api 调用时,graph api 服务器端将对其进行验证。如果您正在为自己的 api 获取令牌,您可以使用 owin 中间件验证访问令牌或手动验证 JWT 令牌。

以上是关于验证 Azure 广告访问令牌时签名无效,但 id 令牌有效的主要内容,如果未能解决你的问题,请参考以下文章

JWT 令牌的签名无效 - Azure + Spring Boot

如何在 Java 中验证 Azure B2C id 令牌的 JWT 签名?

Azure AD Msal 拦截器访问令牌

无法验证从 Azure AD 获得的访问令牌签名以保护 Web API

验证 Azure AD 令牌签名失败 JAVA

验证Azure AD令牌签名失败JAVA