Microsoft Graph API를 사용하여 Azure ADB2C 테넌트에서 사용자 프로필 속성 추출

14290 단어 devopsazure
오늘 Azure Active Directory B2C 인스턴스에서 사용자 목록을 검색해야 했습니다. Azure UI를 통해 갈 수 있다고 생각했지만 데이터의 짧은 페이지와 제한된 속성으로 제한됩니다.

UI에 CSV 내보내기가 제공되지만 사용자의 로그인 이메일 주소가 필요한 경우 csv 출력에서 ​​필요한 ID 개체를 얻지 못합니다.

필요한 것을 얻으려면 Microsoft Graph API를 사용해야 했습니다. 이것은 약간 해키하지만 트릭을 수행합니다!

그래프 API로 인증



Microsoft Graph API는 Office 365 제품군의 사용자와 작업하는 데 권장되는 방법입니다. Azure AD B2C는 동일한 API를 사용합니다. 사용자와 효율적으로 작업하려면 Graph API를 사용해야 합니다.

Graph API에는 사용자와 작업할 수 있는 권한이 있는 테넌트의 애플리케이션에 대한 액세스 토큰이 필요합니다. 사용자 목록을 검색하려는 경우 이미 애플리케이션이 있을 수 있습니다. 우리가 스스로 권한을 부여할 수 있도록 애플리케이션에 대해 Azure Portal에서 몇 가지 항목을 설정해야 합니다.

Microsoft Graph - Users.ReadWrite.All, openid and offline_access 에서 애플리케이션에 몇 가지 권한을 부여해야 합니다. 그런 다음 "인증서 및 비밀"섹션에서 해당 응용 프로그램에 대한 클라이언트 비밀을 만듭니다. tenantID와 ClientId는 모두 애플리케이션 개요에서 가져옵니다.

Graph API의 Azure AD B2C 사용자 프로필 특성



Azure AD B2C 사용자 프로필에는 기본 응답 개체에 없는 검색할 수 있는 특성이 있습니다. 쿼리에서 지정해야 합니다. 특히 우리는 identities 를 원합니다. Azure AD B2C의 단일 계정에는 여러 로그인 방법이 있을 수 있습니다. ID는 계정에 대한 단일 로그인 방법을 나타냅니다.

사용자 등록 흐름은 emailAddress 유형의 ID를 생성합니다.

다른 모든 가능한 속성과 함께 전체 사양을 볼 수 있습니다here.

Azure AD B2C Graph API의 페이징 결과



그래프 API 응답의 기본 제한은 20개입니다. 최대 999개의 결과를 반환하도록 top 쿼리 매개변수를 설정할 수 있습니다. 그보다 더 많은 결과가 있으면 한 번에 999명의 사용자를 검색해야 합니다.

페이지 결과를 돕기 위해 Graph API는 다음 데이터 세트를 나타내는 커서 URL을 반환합니다. 이것은 응답의 @odata.nextLink 속성에 있습니다. 속성이 없으면 결과의 끝에 도달했음을 의미합니다.

스크립트



모든 구성 데이터가 있으면 스크립트를 작성할 수 있습니다. 아래configurationSettings에 애플리케이션 값을 입력하세요.

/* eslint-disable @typescript-eslint/naming-convention */
import fs from 'fs'
import axios from 'axios'

// replace these with your own values
// most of them can be found on the Azure ADB2C UI
// You'll have to create a client secret
const configurationSettings = {
  tenantId: 'b0...',
  clientId: '333...',
  scope: 'https%3A%2F%2Fgraph.microsoft.com%2F.default',
  clientSecret: 'FhVO..',
  pathToSaveResults: './allUserEmailRecords.json',
}

// This is the url you use to get an access token from
const graphAccessUrl =
  'https://login.microsoftonline.com/' +
  configurationSettings.tenantId +
  '/oauth2/v2.0/token'
const graphTokenBody =
  'client_id=' +
  configurationSettings.clientId +
  '&scope=' +
  configurationSettings.scope +
  '&client_secret=' +
  configurationSettings.clientSecret +
  '&grant_type=client_credentials'
// This is the graph api url we will use to retrieve users.
// We ask for the maximum page size of 999 and we limit the result set to the data we want
// - the id and the identities object.
const graphUsersUrl =
  'https://graph.microsoft.com/V1.0/users?$top=999&$select=id,identities'

// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(async () => {
  try {
    const tokenResponse = await axios.post(graphAccessUrl, graphTokenBody)
    const token = tokenResponse.data?.access_token as string
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let allMappedUsers: any[] = []

    // Here we get the first set of results.
    let graphApiResponse = await axios.get(graphUsersUrl, {
      headers: {
        Authorization: 'Bearer ' + token,
        Accept: 'application/json',
      },
    })

    // map this response in to the set to return later
    allMappedUsers = allMappedUsers.concat(
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      mapUserEmails(graphApiResponse.data.value as Array<any>)
    )

    // Now we check for any pages and we get each page and map it into the
    // full result set if found.
    while (graphApiResponse.data['@odata.nextLink'] !== undefined) {
      const url = graphApiResponse.data['@odata.nextLink']
      graphApiResponse = await axios.get(url, {
        headers: {
          Authorization: 'Bearer ' + token,
          Accept: 'application/json',
        },
      })
      console.log('mapping another page...')
      allMappedUsers = allMappedUsers.concat(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        mapUserEmails(graphApiResponse.data.value as Array<any>)
      )
    }

    fs.writeFileSync(
      configurationSettings.pathToSaveResults,
      JSON.stringify(allMappedUsers)
    )
  } catch (error) {
    console.error(error)
  }
})()

// There are multiple identities. We want to use the one that
// is of type "emailAddress"
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function mapUserEmails(userData: Array<any>) {
  return userData.map((userInstance) => {
    return {
      userId: userInstance.id,
      userEmail: (userInstance.identities as Array<{
        signInType: string
        issuerAssignedId: string
      }>).find((userIdentity) => userIdentity.signInType === 'emailAddress')
        ?.issuerAssignedId,
    }
  })
}


의견이나 질문이 있으면 알려주세요!

좋은 웹페이지 즐겨찾기