Azure AD Cypress认证报错AADSTS9002327,求助解决方法
解决Cypress测试Azure AD SPA应用时的AADSTS9002327错误
错误原因
AADSTS9002327错误的核心是:Azure AD为**单页应用(SPA)颁发的令牌,仅允许通过跨域请求(XHR/fetch)**进行兑换,而直接通过cy.origin模拟登录后,Cypress的环境无法正确触发SPA所需的跨域令牌交换流程,导致认证失败。
解决方案:模拟SPA的OAuth2授权码流+PKCE
SPA类型的Azure AD应用必须使用授权码流+PKCE进行认证,我们需要在Cypress中手动模拟这一流程,确保令牌交换符合Azure AD的要求。
步骤1:编写Cypress登录命令
在cypress/support/auth.js中添加以下命令:
// 生成PKCE所需的code_verifier function generateCodeVerifier() { const array = new Uint32Array(56 / 2); window.crypto.getRandomValues(array); return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); } // 基于code_verifier生成code_challenge function generateCodeChallenge(codeVerifier) { return crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)) .then(buffer => btoa(String.fromCharCode(...new Uint8Array(buffer)))) .then(base64 => base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')); } // 封装Azure AD登录命令 Cypress.Commands.add('loginToAzureAD', async () => { const tenantId = Cypress.env('azureTenantId'); const clientId = Cypress.env('azureClientId'); const redirectUri = Cypress.env('azureRedirectUri'); const scope = 'openid profile email'; const username = Cypress.env('azureUsername'); const password = Cypress.env('azurePassword'); // 生成PKCE参数 const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); // 访问Azure AD授权端点 cy.visit(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?` + `client_id=${clientId}&` + `response_type=code&` + `redirect_uri=${encodeURIComponent(redirectUri)}&` + `scope=${encodeURIComponent(scope)}&` + `code_challenge_method=S256&` + `code_challenge=${codeChallenge}`); // 在Azure AD域名下处理登录交互 cy.origin('login.microsoftonline.com', { args: { username, password } }, ({ username, password }) => { // 输入邮箱 cy.get('input[type="email"]').should('be.visible').type(username); cy.get('input[type="submit"]').should('be.visible').click(); // 输入密码 cy.get('input[type="password"]').should('be.visible').type(password); cy.get('input[type="submit"]').should('be.visible').click(); // 跳过"保持登录"提示 cy.get('#idBtn_Back').should('be.visible').click(); }); // 从重定向URL中提取授权码 cy.url().then(url => { const code = new URL(url).searchParams.get('code'); expect(code).to.not.be.null; // 通过跨域请求兑换令牌 cy.request({ method: 'POST', url: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, form: true, body: { client_id: clientId, grant_type: 'authorization_code', code: code, redirect_uri: redirectUri, code_verifier: codeVerifier, scope: scope } }).then(response => { // 将令牌存入localStorage,模拟SPA的存储逻辑 cy.window().then(window => { window.localStorage.setItem('access_token', response.body.access_token); window.localStorage.setItem('id_token', response.body.id_token); }); }); }); });
步骤2:配置Cypress环境变量
在cypress.config.js或cypress.env.json中添加敏感配置:
{ "azureTenantId": "你的租户ID", "azureClientId": "SPA应用的客户端ID", "azureRedirectUri": "应用注册中设置的重定向URI", "azureUsername": "测试账号邮箱", "azurePassword": "测试账号密码" }
步骤3:在测试用例中使用登录命令
describe('Azure AD 保护的SPA应用测试', () => { beforeEach(() => { // 使用session持久化登录状态,避免重复登录 cy.session('azure-ad-session', () => { cy.loginToAzureAD(); }, { validate() { cy.visit('/'); cy.contains('欢迎页内容').should('be.visible'); } }); cy.visit('/'); }); it('应该成功加载受保护页面', () => { cy.get('.protected-content').should('be.visible'); }); });
关键注意事项
- 确保Azure AD应用注册类型为单页应用(SPA),且重定向URI与配置中的
azureRedirectUri完全一致 - 开启Cypress的跨域支持:在
cypress.config.js中设置chromeWebSecurity: false(仅测试环境使用) - 不要硬编码敏感信息,始终使用Cypress环境变量存储
- 如果应用有自定义的令牌存储键名,需要对应修改
localStorage.setItem的键
内容的提问来源于stack exchange,提问作者teddy
相关产品推荐
相关产品推荐

