【问题标题】:Invalid signature while validating Azure ad access token, but id token works验证 Azure 广告访问令牌时签名无效,但 id 令牌有效
【发布时间】: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导致验证失败。

标签: validation oauth-2.0 azure-active-directory access-token adal


【解决方案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 令牌。
    猜你喜欢
    • 2017-12-15
    • 2021-08-10
    • 2011-10-25
    • 1970-01-01
    • 2021-03-16
    • 2017-10-16
    • 2022-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多