You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过SAML 2.0与ADFS实现Vue 2 SPA身份认证?

基于Vue 2 SPA与.NET Core API的ADFS 5.0 SAML SSO实现方案

1. 初始配置的创建位置与内容

SPA端(Vue 2)

  • 选择兼容浏览器的SAML客户端库,比如saml2-js或js-saml,通过npm安装
  • 在项目中创建独立的SAML配置文件(如src/config/saml.js),填入以下核心配置:
    • idpEntityId: 从ADFS元数据中获取的IDP实体ID
    • idpSsoUrl: 元数据里的SingleSignOnService地址(HTTP-POST绑定)
    • callbackUrl: 你的SPA认证回调地址(如https://your-spa-domain.com/saml/callback,需和ADFS端配置一致)
    • idpCert: 从ADFS元数据提取的签名证书,转换为PEM格式(去掉元数据里的证书首尾标识,保留中间Base64内容并添加-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----)

.NET Core API端

  • 在appsettings.json中添加SAML相关配置,包括IDP实体ID、签名证书,以及允许的SPA回调域名
  • 配置CORS策略,允许ADFS服务器和SPA域名的跨域请求,确保回调时不会被拦截

2. 跳转路由的设置方法

路由配置

  • 在Vue路由文件(src/router/index.js)中添加回调页面路由:
    {
      path: '/saml/callback',
      name: 'SamlCallback',
      component: () => import('@/views/SamlCallback.vue')
    }
    
  • 全局路由守卫拦截未登录请求:
    router.beforeEach((to, from, next) => {
      const isAuthenticated = !!localStorage.getItem('user')
      if (!isAuthenticated && to.meta.requireAuth !== false) {
        // 触发SAML登录跳转
        const samlClient = require('@/config/saml').default
        const loginUrl = samlClient.createLoginRequestUrl({
          relayState: to.fullPath // 保存当前页面路径,登录后跳转回来
        })
        window.location.href = loginUrl
      } else {
        next()
      }
    })
    

手动触发登录

  • 在登录按钮或首页入口添加跳转逻辑:
    methods: {
      redirectToSso() {
        const samlClient = require('@/config/saml').default
        const loginUrl = samlClient.createLoginRequestUrl({
          relayState: '/'
        })
        window.location.href = loginUrl
      }
    }
    

3. ADFS端的配置要点(需同步给ADFS管理团队)

  • 提供你的SPA作为**服务提供商(SP)**的实体ID(自定义,如https://your-spa-domain.com/saml/metadata)
  • 提交精确的回调URL(必须和SPA配置的callbackUrl完全一致)
  • 要求启用HTTP-POST绑定用于SAML响应传输(前端无法处理HTTP-Redirect绑定的大响应)
  • 确认ADFS返回的SAML断言中包含你需要的用户属性,比如用户名、邮箱、角色/权限标识(可指定ADFS的声明规则,将AD组映射为角色声明)
  • 要求将你的SPA域名加入ADFS的允许回调列表,避免跨域拦截

4. 解析ADFS响应并确定用户权限等级

回调页面处理逻辑

在SamlCallback.vue组件中处理ADFS返回的SAML响应:

async mounted() {
  // 获取POST传递的SAMLResponse参数
  const samlResponse = document.forms[0]?.SAMLResponse.value || this.$route.query.SAMLResponse
  if (!samlResponse) {
    this.$router.push('/login-error')
    return
  }

  // 初始化SAML客户端
  const samlClient = require('@/config/saml').default
  try {
    // 验证并解析响应
    const parsedResponse = await samlClient.validatePostResponse({
      requestBody: { SAMLResponse: samlResponse }
    })

    // 提取用户信息与权限
    const userInfo = {
      username: parsedResponse.user.nameID,
      email: parsedResponse.user.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
      roles: parsedResponse.user.attributes['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] || []
    }

    // 存储用户信息(可选择Vuex或localStorage)
    localStorage.setItem('user', JSON.stringify(userInfo))
    this.$store.commit('auth/setUser', userInfo)

    // 跳转到之前保存的页面
    const relayState = this.$route.query.RelayState || '/'
    this.$router.push(relayState)
  } catch (error) {
    console.error('SAML响应解析失败:', error)
    this.$router.push('/login-error')
  }
}

后端二次验证(可选但推荐)

将解析后的SAML断言发送到.NET Core API,后端使用Microsoft.IdentityModel.Protocols.Saml2库再次验证签名和断言有效性,同时从数据库中匹配用户的详细权限配置,返回给SPA确保权限数据可信。

5. 根据权限控制用户应用访问权限

路由级权限控制

  • 在路由配置中添加权限元数据:
    {
      path: '/admin/dashboard',
      name: 'AdminDashboard',
      component: () => import('@/views/admin/Dashboard.vue'),
      meta: {
        requireAuth: true,
        requiredRoles: ['Admin', 'Supervisor']
      }
    }
    
  • 在全局路由守卫中校验权限:
    router.beforeEach((to, from, next) => {
      const user = JSON.parse(localStorage.getItem('user')) || {}
      if (to.meta.requiredRoles) {
        const hasPermission = to.meta.requiredRoles.some(role => user.roles?.includes(role))
        if (!hasPermission) {
          next('/403')
          return
        }
      }
      next()
    })
    

页面元素级权限控制

  • 封装权限判断方法或自定义指令:
    // 在Vue原型上挂载权限判断方法
    Vue.prototype.$hasRole = function(role) {
      const user = JSON.parse(localStorage.getItem('user')) || {}
      return user.roles?.includes(role)
    }
    
  • 在页面中使用:
    <el-button v-if="$hasRole('Admin')" type="danger" @click="deleteData">删除数据</el-button>
    <el-menu-item v-if="$hasRole('Editor')" index="/content/edit">内容编辑</el-menu-item>
    

内容的提问来源于stack exchange,提问作者SemiproCCXI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:03:50