如何通过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实体IDidpSsoUrl: 元数据里的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
相关产品推荐
相关产品推荐

