【问题标题】:Calling Azure Function App from static-file SPA从静态文件 SPA 调用 Azure Function App
【发布时间】:2020-04-25 12:05:46
【问题描述】:

回答:对于寻找此答案的任何人,我都可以通过在 MSAL 获取令牌调用范围内指定 Web API 来获得所需的令牌,如下所示:

let token = msal.acquireToken({ 
  scopes: [ 'myFunctionApp.azurewebsites.net/user_impersonation' ] 
})

完成此操作后,我将令牌用作身份验证标头中的不记名令牌。除了调用 MS Graph 端点外,我还可以使用它。我在这里一个安静的小地方找到了这些信息:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#request-specific-scopes-for-a-web-api

非常感谢@StanleyGong 的帮助,最终得到了答案。

/////

过去几天我一直在寻找如何有效保护从 Azure Web 应用调用到 Azure Function App 提供的 Vue SPA。我为 Web 应用程序和函数应用程序都打开了 Easy Auth,我正在调用 /.auth/me 端点来获取一个 id 令牌,我读过它可以用作不记名令牌。所以我正在做的是调用/.auth/me 并使用返回的id_token 创建一个授权标头,但在调用函数应用程序时我仍然得到一个401 Unauthorized

从 /.auth/me 获取 id_token 并将其作为默认标头添加到所有 Axios 调用的代码(我确实认识到这需要刷新...我将在我得到之后创建所有这些逻辑一个电话工作):

let authUrl = '/.auth/me';
let response = await axios.get(authUrl);
let token = response.data[0].id_token;
axios.defaults.headers.common['Authorization'] = "Bearer " + token;

我可以看到请求中 Authorization 标头中使用的令牌,紧跟上面的部分:

Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Authorization: Bearer eyJ0eXAiO...

我看到这篇文章并尝试使用其中的一些信息,包括 Chris Gillum 的博客文章(很有帮助,但仍然没有让我到达那里):Authentication for Azure Functions

关于如何使它工作的任何建议?我觉得我很接近,但我并不完全在那里。如果这不是正确的解决方法,任何建议也会有所帮助。

总的来说,我在身份方面非常缺乏经验,所以我一直在使用术语,但文档中的不一致也无济于事。

另外,有没有什么方法可以测试这些东西,而无需在每次更改时都部署代码?如果代码不在 Web 应用服务器上,可以通过某种方式从商店获取令牌来测试这一点会很棒……我猜是一厢情愿,但只是想知道。

编辑:我刚刚意识到我读过的所有帖子都表明访问令牌是从/.auth/me 返回的,但我没有得到。返回的 JSON 如下所示,仅此而已:

id_token: "eyJ0eXA...7_A"
provider_name: "aad"
user_claims: [{typ: "aud", val: "2...fa"},…]
user_id: "<<my email address>>"

另一个编辑:我发现我可以使用 MSAL 获取令牌,但它包含的信息与 /.auth/me 的信息略有不同。使用任一令牌作为不记名令牌仍会导致 401。我确实注意到在应用程序的 AAD 身份验证设置中,颁发者 URL 不同。一个是 sts.windows.net,另一个是 login.windows.net。两人后面都有租户 ID。不确定这是否有所不同,但我尝试将它们设置为相同的值,但没有帮助。

/.auth/me 令牌(当然是经过消毒的):

{
  "aud": "2fe...fa", (AAD app id)
  "iss": "https://sts.windows.net/<< AD tenant id >>/",
  "iat": 15785xxx,
  "nbf": 15785xxx,
  "exp": 15785xxx,
  "aio": "AVQAq/...UQ=",
  "amr": [
    "pwd",
    "mfa"
  ],
  "family_name": "<< my last name >>",
  "given_name": "<< my first name >>",
  "ipaddr": "<< my ip >>",
  "name": "<< my full name >>",
  "nonce": "e32a...48",
  "oid": "a0...0e",
  "sub": "LNu...8l8",
  "tid": "f14...2ca",
  "unique_name": "<< my email >>",
  "upn": "<< my email >>",
  "uti": "i9O...TAQ",
  "ver": "1.0"
}

MSAL 访问令牌:

{
  "aud": "000...000", (mostly 0s...not sure what this id is)
  "iss": "https://sts.windows.net/<< AD tenant id >>",
  "iat": 15785xxx,
  "nbf": 15785xxx,
  "exp": 15785xxx,
  "acct": 0,
  "acr": "1",
  "aio": "AVQAq/8O...ZZ12s=", (different than above)
  "amr": [
    "pwd",
    "mfa"
  ],
  "app_displayname": "<< app name in AAD app registration >>",
  "appid": "<< app GUID from AAD >>",
  "appidacr": "0",
  "family_name": "<< my last name >>",
  "given_name": "<< my first name >>",
  "ipaddr": "<< my ip >>",
  "name": "<< my full name >>",
  "oid": "a0...0e", (same as above)
  "platf": "3",
  "puid": "10...1B",
  "scp": "User.Read profile openid email",
  "signin_state": [
    "kmsi"
  ],
  "sub": "WuX...L3A",
  "tid": "f14...2ca", (tenant id, same as above)
  "unique_name": "<< my email >>",
  "upn": "<< my email >>",
  "uti": "UD...AA",
  "ver": "1.0",
  "xms_st": {
    "sub": "LNu...8l8"
  },
  "xms_tcdt": 14...37
}

【问题讨论】:

  • 您应该会收到一个cookie,该cookie将在后续请求中自动发送,但是如果您遇到问题,您可以使用作为不记名令牌返回的id_token(发送标头@987654337 @ 请求)。
  • 所以基本上,您想知道如何从托管在 Azure 应用服务上的 VUE SPA 应用调用启用轻松身份验证的 Azure 函数,对吧?
  • @Icehorn 我尝试将 id_token 作为不记名令牌传递,如帖子中的第二个代码块所示。该代码块是从 Chrome 开发工具复制/粘贴到请求的响应标头上的。我仍然收到 401 Unauthorized 响应。
  • @StanleyGong 是的,没错。 Azure Web 应用程序 (Vue SPA) -> Azure Functions 应用程序。两者都托管在 Azure 中,都在同一个 Azure AD 租户上启用了 Easy Auth

标签: azure azure-functions azure-web-app-service easy-auth


【解决方案1】:

对于您的方案,您可以在租户中注册 Azure AD 本机应用程序作为客户端,以获取 access_token 作为承载令牌以调用您的 Azure 函数。如果您使用的是服务到服务调用,请参考my previous post here

如果您要登录用户,您也应该在您的租户中注册一个 Azure AD 应用程序并使用其他配置: 为该应用添加 user_impersonation 权限,以便用户可以登录您的应用并调用您的 Azure 函数(请在此处记下您的 azure 函数应用程序 ID,我们稍后将使用它): 添加此权限并授予它:

完成这些步骤后,您的新应用程序将能够登录用户调用您的 Azure 函数。

我不确定你的 VUE 应用程序代码是什么样的,但这是一个VUE adal sample,我的演示将基于它。 1. 下载代码并前往src/main.js将其内容替换为以下代码:

import Vue from 'vue'
import axios from 'axios'
import { default as Adal, AxiosAuthHttp } from 'vue-adal'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false
const functionBase = `<your Azure function URL>`
const functionResource = '<your Azure function application ID>'

Vue.use(Adal, {
  config: {
    tenant: '<your tenant ID>',
    clientId: '<your new resistered app ID>',
    redirectUri: '<redirect url you mapped in your new resgistered app ID, in this case it should be : http://localhost:8080>',
    cacheLocation: 'localStorage'
  },
  requireAuthOnInitialize: true,
  router: router
})

Vue.use({
  install (vue, opts = {}) {
    // Configures an axios http client with a interceptor to auto-acquire tokens
    vue.prototype.$functionApi = AxiosAuthHttp.createNewClient({
      // Required Params
      axios: axios,
      resourceId: functionResource, // Resource id to get a token against

      // Optional Params
      router: router, // Enables a router hook to auto-acquire a token for the specific resource

      baseUrl: functionBase, // Base url to configure the client with

      onTokenSuccess (http, context, token) { // Token success hook
        // When an attempt to retrieve a token is successful, this will get called.
        // This enables modification of the client after a successful call.
        if (context.user) {
          // Setup the client to talk with the Microsoft Graph API
          http.defaults.baseURL = `${functionBase}`
          console.log(token)
        }
      },

      onTokenFailure (error) { // Token failure hook
        // When an attempt to retrieve a token is not successful, this will get called.
        console.log(error)
      }
    })
  }
})

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

2。 转到src/views/Home.vue,将&lt;script&gt;部分的内容替换为以下代码:

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'home',
  components: {
    HelloWorld
  },
  data () {
    return {
      msg: "Signing in..."
    }
  },
  async created () {
    if (this.$adal.isAuthenticated()) {
      this.msg = "Hello, " + this.$adal.user.profile.name 

      let functionInfo = await this.getAzureFunctionInfo()
      this.msg += "  |  function result : " + functionInfo


    } else {
      this.msg = "Please sign in"
    }
  },

  methods: {
    async getAzureFunctionInfo () {
      let res = await this.$functionApi.get('',{
        params: {
          'name': 'test'
        }
      })
      console.log(res)
      return res.data
    }
  }
}
</script>

让我们测试一下结果: 由于此项目将在本地运行,您应该转到您的 Azure 功能 => 平台功能 => 所有设置以启用所有 cors 请求,以便您可以从静态 vue 项目在本地测试您的功能:

我的函数逻辑很简单,如果你用参数调用它,它会回复一个 hello 响应:

运行项目,并通过 url 在私有浏览器窗口中打开它:http://localhost:8080 您将被要求登录,您可以看到它可以成功调用 Azure 函数:

我认为这就是您正在寻找的答案。

更新

根据您的代码,您正在使用 Azure AD V2 登录用户,请:

1)按照第二种解决方案重新启动 Azure 广告应用并完成权限授予过程,以便它可以登录用户并访问您的 Azure 功能。

2) 在您的 Azure AD 中,按名称找到代表您的 Azure 函数应用程序的 Azure AD 应用程序(我的 Azure 函数名称是 :stanfuntest ): 在此处复制范围:

2) 在你的 VUE 代码中,使用这个应用 ID 作为客户端 ID 来登录用户,修改代码以获得你刚刚找到的范围的访问令牌:

let token = msal.acquireToken( { scopes: [ 'https://stanfuntest.azurewebsites.net/user_impersonation' ] } )

希望对你有帮助。

【讨论】:

  • 首先,感谢您的精彩回复!这么详细!我相信我想使用你提到的第一种方法,只是调用服务到服务。我已按照您在另一篇文章中的说明进行操作,并且在调用 login.microsoftonline.com 时遇到了 CORS 问题。使用 Postman 发出请求时,我得到了一个访问令牌,并且在 JWT 解码器中读取令牌时,一切看起来都很好。但是,当我从浏览器调用时,出现 CORS 错误。想法?
  • 对不起,这是我的错。作为我基于服务到服务调用的第一种方法,它是针对服务端的。由于您的 VUE 应用程序是一个无法使用它的客户端(Azure AD 不允许 CORS),如果您确实想使用它,您应该实现一个后端作为代理来获取您的 VUE 应用程序的访问令牌。所以请使用第二种方式。
  • 嗨,@TheDoc,我注意到你可以在你的 VUE 项目中获取访问令牌。你能告诉我你是如何得到它的代码吗?似乎您正在获取 microsoft graph access token 而不是您的 Azure function access token 。您只需将资源从“graph.microsoft.com”修改为您的 Azure 函数应用程序 ID,以便您可以获得访问令牌来调用您的 Azure 函数
  • 我能够通过使用msal.acquireToken() 并按如下方式指定范围:let token = msal.acquireToken( { scopes: [ 'myFunctionApp.azurewebsites.net' ] } ) 使一切正常工作。我在这里的一个小段落中找到了此信息:docs.microsoft.com/en-us/azure/active-directory/develop/…。非常感谢你的帮助!虽然我最终没有使用您的确切解决方案,但他们引导我找到了答案。非常感谢!
  • 嗨@TheDoc,我已经在我的答案中附加了我的更新,请检查一下
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 2017-08-25
  • 2020-08-08
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 2020-10-26
  • 2019-07-02
相关资源
最近更新 更多