当前位置:   article > 正文

前端对接微软AAD登录并使用.net webapi验证微软aad token_ad解析token获取用户的url在哪里可以看到

ad解析token获取用户的url在哪里可以看到

大致流程

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('/', '_');
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

二.前端获取code

这一步也很简单,就是把上面所有获取到的参数拼成指向微软的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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

跳转这个链接,登录成功后就会回调到配置的地址,就可获取到code
在这里插入图片描述

三.前端获取Token

注意一个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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

因为我主要写后端的,所以我这里写的前端只涉及到拼接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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

这个站点可以解析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使用自定义范围

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/797906
推荐阅读
  

闽ICP备14008679号