前端对接微软AAD登录并使用.net webapi验证微软aad token

清水截 2024-06-16 17:33:02 阅读 100

大致流程

1.添加应用注册,获取应用程序id,租户id,添加回调地址,添加graphapi权限,添加范围,生成验证字符串

2.跳转微软登录并从回调地址获取code

3.用code获取token

4.验证token

一.前期准备

1.在Microsoft Entra ID(AAD)中新建一个应用注册,或使用之前有的也行

在这里插入图片描述

2.进入应用注册后,记录下应用程序ID(ClientID)和租户ID(TenantID)

在这里插入图片描述

3.在API权限除添加User.Read委托权限,这个权限分类为Microsoft Graph

在这里插入图片描述

4.在公开API处保存应用程序IDURI,并新建一个范围,命名为User.Read,并记录范围全程:格式为api://{ClientID}/{范围},这个是后续的scope参数,必须为应用注册的自定义范围才行,要使用graph的scope,获取到的token就是graph的token,而不是这个应用注册的token,会导致验证token失败

在这里插入图片描述

5.配置回调地址

在身份验证处添加你想要的回调地址,后续获取code时会拼在你指定的回调地址后,比如http://localhost:8099/app.html?code={code},回调地址中不能有#号,会在获取token时匹配失败!

在这里插入图片描述

6.生成证明密钥和验证字符串

string codeVerifier = PkceHelper.GenerateCodeVerifier();//验证字符串生成(后续获取token使用)string codeChallenge = PkceHelper.GenerateCodeChallenge(codeVerifier);//证明密钥(获取Code使用)public class PkceHelper{ public static string GenerateCodeVerifier() { using (var rng = new RNGCryptoServiceProvider()) { var byteArray = new byte[32]; rng.GetBytes(byteArray); return Convert.ToBase64String(byteArray) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } } public static string GenerateCodeChallenge(string codeVerifier) { using (var sha256 = SHA256.Create()) { var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); return Convert.ToBase64String(challengeBytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } }}

二.前端获取code

这一步也很简单,就是把上面所有获取到的参数拼成指向微软的url,跳转并登录后,拿到回调地址后面的code

let tenant = '11111111-2222-3333-4444-555553957ae4';//租户idlet client_id = '22211111-2222-3333-4444-555553957ae4';//应用程序idlet redirect_uri = 'http%3A%2F%2Flocalhost:8099%2Fapi.html';//uri转码后回调地址let scope = 'api://22211111-2222-3333-4444-555553957ae4/User.Read';//范围let response_type = 'code';//默认codelet code_challenge = 'testtesttest';//证明密钥let state = 'test';//自定义状态let url = 'https://login.microsoftonline.com/'+tenant+'/oauth2/v2.0/authorize?client_id='+client_id+'&response_type='+response_type+'&scope='+scope+'&redirect_uri='+redirect_uri+'&response_mode=query&state='+state+'&code_challenge='+code_challenge+'&code_challenge_method=S256';//拼接url

跳转这个链接,登录成功后就会回调到配置的地址,就可获取到code

在这里插入图片描述

三.前端获取Token

注意一个code只能使用一次

const tenantId = '11111111-2222-3333-4444-555553957ae4';//租户idconst clientId = '22211111-2222-3333-4444-555553957ae4';//应用程序idconst redirectUri = 'http://localhost:8099/api.html';//回调地址,因为是接口请求,所以不用转码const authorizationCode = '0.AT4AyT91testestlkcwZ0BDnf3Op-z2YZYAIc.AgABBAIAAADnfolhJpSnRYB1SVj-Htesttestetestet_6xfpglv1V'; //填入上一步的code,要注意别把state或其他参数跟在后面const codeVerifier = 'test1test1'; //验证字符串,第一步第6点生成的const tokenEndpoint = `https://login.microsoftonline.com/${ tenantId}/oauth2/v2.0/token`;//请求路径//请求const body = new URLSearchParams({ client_id: clientId,grant_type: 'authorization_code',code: authorizationCode,redirect_uri: redirectUri,code_verifier: codeVerifier // 如果使用 PKCE});try{ const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() }); if (!response.ok) { throw new Error(`HTTP error! status: ${ response.status }`); } const data = await response.json(); console.log('Access Token:', data.access_token);//打印Token}catch (error){ console.error('Error fetching token:', error);}

因为我主要写后端的,所以我这里写的前端只涉及到拼接url和调用微软接口,具体页面和回调后获取token的后续操作可以自定义实现,不过大致也是进入web系统后,先判断本地存储里有没有token,有的话判断一下token是否过期,没有或者过期则拼接微软url,跳转登录后,从回调地址获取到code,再用code获取token,之后发给后端验证。

四.后端验证token

这里用的.net webapi来进行验证,因为微软的token有效期只有一个多小时,我的想法是用微软token去兑换自定义token,这样就不用频繁调用微软api来刷新或者重新登录获取token

验证微软token

注释里有些提示很重要

/// <summary>/// 验证微软Token/// </summary>/// <returns></returns>public async Task<bool> AuthMicToken(string token){ //下方都是配置项 string azureAdInstance = configuration["AzureAd:Instance"];//实例,一般是https://login.microsoftonline.com/,具体可以看token解析后的iss字段里租户id前的域名 string validIssuer = configuration["AzureAd:ValidIssuer"];//颁发者,一般是https://login.microsoftonline.com/{tenantId }/v2.0 这个格式,具体可以看token解析后的iss字段,验证不成功可能是这个字段不匹配,需要和token的iss完全一致 string tenantId = configuration["AzureAd:TenantId"];//租户id string audience = configuration["AzureAd:ClientId"];//接收者,一般是应用程序id,具体可以看token解析后的aud字段,验证不成功可能是这个字段不匹配,需要和token的aud完全一致 string emailField = configuration["AzureAd:EmailField"];//业务要求获取token里邮箱对应字段 validIssuer = string.Format(validIssuer, tenantId); if (token.StartsWith("Bearer ")) { token = token.Substring("Bearer ".Length).Trim(); }//定义验证参数 var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidAudiences = new List<string>(){ audience }, ValidIssuer = validIssuer, IssuerSigningKeys = GetSigningKeys(azureAdInstance, tenantId), }; try { //这里验证token,验证失败就会抛异常 var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken); return true; } catch (Exception ex) { logger.LogError(ex, "验证微软token失败"); return false; }}//获取密钥public static IEnumerable<SecurityKey> GetSigningKeys(string azureAdInstance, string tenantId){ // Retrieve the public keys from the OpenID Connect metadata endpoint string openIdConnectMetadataUrl = $"{ azureAdInstance}{ tenantId}/v2.0/.well-known/openid-configuration"; var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(openIdConnectMetadataUrl, new OpenIdConnectConfigurationRetriever()); OpenIdConnectConfiguration openIdConfig = configurationManager.GetConfigurationAsync().Result; return openIdConfig.SigningKeys;}

这个站点可以解析token:https://jwt.io/

token解析示例:

在这里插入图片描述

参考网站

微软官方文档

https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-auth-code-flow

在这里插入图片描述

踩雷

验证token失败: IDX10511,原因是scope没有使用自定义范围

https://stackoverflow.com/questions/69589324/securitytokeninvalidsignatureexception-idx10511

获取code失败:需要添加验证字符串

获取token失败:报需要跨域时,不能使用api调用软件,postman等

验证token失败:验证者和颁发者必须完全匹配!

验证token失败:token解析后aud为 "00000003-0000-0000-c000-000000000000"时为graph的token,必须scope使用自定义范围



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。