赞
踩
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('/', '_'); } } }
这一步也很简单,就是把上面所有获取到的参数拼成指向微软的url,跳转并登录后,拿到回调地址后面的code
let tenant = '11111111-2222-3333-4444-555553957ae4';//租户id
let client_id = '22211111-2222-3333-4444-555553957ae4';//应用程序id
let redirect_uri = 'http%3A%2F%2Flocalhost:8099%2Fapi.html';//uri转码后回调地址
let scope = 'api://22211111-2222-3333-4444-555553957ae4/User.Read';//范围
let response_type = 'code';//默认code
let 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
注意一个code只能使用一次
const tenantId = '11111111-2222-3333-4444-555553957ae4';//租户id const clientId = '22211111-2222-3333-4444-555553957ae4';//应用程序id const 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,之后发给后端验证。
这里用的.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使用自定义范围
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。