Azure应用注册与配置

1. 进入应用注册页面

登录 Microsoft Entra 管理中心(或 Azure 门户),在左侧菜单中依次选择 “标识” (或 “Microsoft Entra ID”)→ “应用程序”“应用注册” ,然后点击 “+ 新建注册”

2. 填写应用注册信息

在 “注册应用程序” 页面中:

  • 名称:输入一个有意义的应用名称(例如 vue-azure-login),用户可能会看到此名称。
  • 支持的帐户类型:根据你的需求选择。如果你只允许自己组织内的账户登录,选择 “仅此组织目录中的帐户(单租户)” ;如果希望任何组织的账户都能登录,选择 “任何组织目录中的帐户(多租户)”
  • 重定向 URI这一步对后续登录跳转至关重要。在平台下拉框中选择 “单页应用程序(SPA)” ,然后输入你的 Vue 应用地址,例如本地开发环境为 http://localhost:5173
  • 点击 “注册” 按钮完成创建。

3. 记录必要信息

注册完成后,页面会自动跳转到应用 “概述” 页。请记录下以下两个关键值,后续需要填入 auth.js 配置中:

  • 应用程序(客户端)ID:唯一标识此应用的 ID。
  • 目录(租户)ID:你的租户 ID。

前端项目集成

npm install @azure/msal-browser

配置.env

以本地测试为例

VITE_AZURE_CLIENT_ID=应用程序(客户端) ID
VITE_AZURE_TENANT_ID=目录(租户) ID
VITE_AZURE_REDIRECT_URI=http://localhost:5173
VITE_AZURE_POST_LOGOUT_REDIRECT_URI=http://localhost:5173

再随便写几行用于auth的工具(/src/utils/auth.js

import {
  PublicClientApplication,
  InteractionRequiredAuthError,
  LogLevel,
} from '@azure/msal-browser'

// ============ MSAL 配置 ============
const msalConfig = {
  auth: {
    clientId: import.meta.env.VITE_AZURE_CLIENT_ID,
    authority: `https://login.microsoftonline.com/${import.meta.env.VITE_AZURE_TENANT_ID}`,
    // 登录后重定向回应用的地址,必须与 Azure 应用注册中的重定向 URI 一致
    redirectUri: import.meta.env.VITE_AZURE_REDIRECT_URI || window.location.origin,
    postLogoutRedirectUri: import.meta.env.VITE_AZURE_POST_LOGOUT_REDIRECT_URI || window.location.origin,
  },
  cache: {
    cacheLocation: 'localStorage',        // 持久化缓存,刷新页面保持登录
    storeAuthStateInCookie: false,
  },
  system: {
    loggerOptions: {
      loggerCallback(level, message, containsPii) {
        if (containsPii) return
        switch (level) {
          case LogLevel.Error:   console.error(message); break
          case LogLevel.Warning: console.warn(message); break
          case LogLevel.Info:    console.info(message); break
          case LogLevel.Verbose: console.debug(message); break
        }
      },
      logLevel: LogLevel.Warning,
    },
  },
}

// 请求的权限范围(登录获取用户基本信息)
const loginRequest = {
  scopes: ['openid', 'profile', 'User.Read'],
}

// 创建 MSAL 实例
const msalInstance = new PublicClientApplication(msalConfig)

// ============ 初始化(必须在使用前调用一次) ============
let isInitialized = false

export async function initAuth() {
  if (isInitialized) return

  await msalInstance.initialize()

  // 处理重定向返回的结果(从微软登录页跳回来时会执行)
  try {
    const response = await msalInstance.handleRedirectPromise()
    if (response) {
      // 登录成功,设置当前活跃账户
      msalInstance.setActiveAccount(response.account)
      console.info('[Auth] 重定向登录成功:', response.account.username)
    } else {
      // 非重定向返回,尝试从缓存恢复活跃账户
      const accounts = msalInstance.getAllAccounts()
      if (accounts.length > 0) {
        msalInstance.setActiveAccount(accounts[0])
      }
    }
  } catch (error) {
    console.error('[Auth] 处理重定向响应失败:', error)
  }

  isInitialized = true
}

// ============ 获取当前活跃账户 ============
export function getActiveAccount() {
  return msalInstance.getActiveAccount()
}

// ============ 是否已登录 ============
export function isAuthenticated() {
  return msalInstance.getAllAccounts().length > 0
}

// ============ 登录(重定向到微软登录页) ============
export async function login() {
  // 如果已有账户,先尝试静默获取令牌
  const accounts = msalInstance.getAllAccounts()
  if (accounts.length > 0) {
    msalInstance.setActiveAccount(accounts[0])
    try {
      const result = await msalInstance.acquireTokenSilent({
        ...loginRequest,
        account: accounts[0],
      })
      return result
    } catch (error) {
      if (error instanceof InteractionRequiredAuthError) {
        // 静默失败,走重定向登录
        await msalInstance.loginRedirect(loginRequest)
      } else {
        throw error
      }
    }
  } else {
    // 无账户,直接重定向到微软登录页
    await msalInstance.loginRedirect(loginRequest)
  }
}

// ============ 登出 ============
export async function logout() {
  const account = msalInstance.getActiveAccount()
  await msalInstance.logoutRedirect({
    account,
    postLogoutRedirectUri: msalConfig.auth.postLogoutRedirectUri,
  })
}

// ============ 获取访问令牌(用于调用受保护的 API) ============
export async function getAccessToken(scopes = ['User.Read']) {
  const account = msalInstance.getActiveAccount()
  if (!account) {
    throw new Error('用户未登录')
  }

  try {
    const result = await msalInstance.acquireTokenSilent({
      scopes,
      account,
    })
    return result.accessToken
  } catch (error) {
    if (error instanceof InteractionRequiredAuthError) {
      // 需要用户交互(如令牌过期),走重定向
      await msalInstance.acquireTokenRedirect({ scopes, account })
    } else {
      throw error
    }
  }
}

// ============ 导出 msalInstance(供需要时直接使用) ============
export { msalInstance, loginRequest }

main.js中初始化一下msal实例

import { initAuth } from './utils/auth'
await initAuth()

最后在路由守卫中加上必要的鉴权逻辑,进行登录跳转即可

import { isAuthenticated, login } from '../utils/auth'

// 全局前置守卫,这里可以加入用户登录判断
router.beforeEach(async (to, from, next) => {
    const message = window.message
    if (import.meta.env.VITE_SECURE === 'true') {
        // 检查是否有token
        if (isAuthenticated()) {
            // 如果有token,正常导航
            next()
        } else {
            // 如果没有token,导航到登录页面
            message.info("身份校验失败,SSO登录中...")
            await login()
        }
    }else{
        // 继续前进 next()
        // 返回 false 以取消导航
        next()
    }
})

这时打开页面会进行登录页面的跳转

大功告成