Azure AD认证和Azure AD B2C的token获取
工作当中使用过Azure AD认证和B2C的认证,今天抽时间再回顾一下。
个人理解比较浅显,我认为Azure AD和Azure AD B2C都可作为用户管理的系统,他们提供了自己的登录认证画面,统一使用Graph API对自己的用户和其他功能做管理。
Azure AD功能强大,微软的老牌认证方式,可以很方便跟其他三方应用集成,可做单点登录。
而Azure AD B2C更像是三方的用户系统,最大的特点是可自定义UI画面。
感觉总结的不是很好,纯纯自己的理解,这里就不多说了,让我们进入正题。
这里主要贴一下,当时使用的认证相关获取token的代码。
一、Azure AD
1、获取认证登录地址
下面的代码是nodejs的示例,需要用到msal-node
库·,当然可以根据官网自己拼接URL也是可以的,在之前曾经尝试过,没有任何问题。
只是在当时不知道为什么,老是提示我需要change_code
和verifier
两个参数,因为不明白这两个参数的原理,所以采用了msal-node
库去生成。
msal参考地址: https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html
const msal = require("@azure/msal-node");
function getAuthCodeUrl (){
return new Promise((resolve, reject) => {
const cryptoProvider = new msal.CryptoProvider();
cryptoProvider.generatePkceCodes().then(({ verifier, challenge }) => {
const publiClient= new msal.PublicClientApplication({
auth: {
"clientId": SETTINGS.AD_CLIENT_ID,
"authority": `https://login.microsoftonline.com/${SETTINGS.AD_TENANT_ID}`
}
});
publiClient.getAuthCodeUrl({
scopes: ["user.read"],
redirectUri: SETTINGS.REDIRECT_URL,
codeChallenge: challenge,
codeChallengeMethod: "S256"
}).then((response) => {
let result = {
authUrl: response,
verifier: verifier,
};
resolve(result);
}).catch((error) => {
reject(JSON.stringify(error));
});
});
});
}
2、access_token的获取
① 通过认证code获取access_token。
在通过认证地址登录后,会跳转到重定向地址,附带client_info
和code
参数,verifier
参数由1、中获取。
官方参考地址: https://learn.microsoft.com/zh-cn/graph/auth-v2-user
const rp = require("request-promise");
function getADToken (code, clientInfo, verifier) {
return new Promise((resolve, reject) => {
let options = {
method: "POST",
uri: `https://login.microsoftonline.com/${SETTINGS.AD_TENANT_ID}/oauth2/v2.0/token`,
form: {
"client_id": SETTINGS.AD_CLIENT_ID,
"scope": "openid offline_access profile",
"grant_type": "authorization_code",
"redirect_uri": SETTINGS.REDIRECT_URL,
"code": code,
"client_secret": SETTINGS.AD_SECRIT_ID,
"client_info": clientInfo,
"code_verifier": verifier,
},
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
rp(options)
.then((res) => {
resolve(JSON.parse(res)["access_token"]);
})
.catch((err) => {
reject(err);
});
});
}
二、Azure AD B2C
1、获取认证登录地址
// B2C配置对象
const b2cConfig= {
auth: {
clientId: SETTINGS.B2C_CLIENT_ID,
authority: `https://${SETTINGS.B2C_TENANT_NAME}.b2clogin.com/${SETTINGS.B2C_TENANT_NAME}.onmicrosoft.com/${SETTINGS.B2C_POLICY}`,
knownAuthorities: [`${SETTINGS.B2C_TENANT_NAME}.b2clogin.com`],
redirectUri: SETTINGS.REDIRECT_URL,
}
};
// 创建msal对象
const publicClient = new msal.PublicClientApplication(b2cConfig);
function getAuthCodeUrl (){
return new Promise((resolve, reject) => {
const cryptoProvider = new msal.CryptoProvider();
cryptoProvider.generatePkceCodes().then(({ verifier, challenge }) => {
publicClient.getAuthCodeUrl({
redirectUri: b2cConfig.auth.redirectUri,
authority: b2cConfig.auth.authority,
scopes: ["openid", "offline_access"],
state: "login",
codeChallenge: challenge,
codeChallengeMethod: "S256",
}).then((response) => {
let result = {
authUrl: response,
verifier: verifier,
};
resolve(result);
}).catch((error) => {
reject(JSON.stringify(error));
});
});
});
}
2、获取IdToken
B2C认证拿到的code只能换取idToken, 就算拿到了access_token也是属于web_token,不能用于调用graph api。这是跟微软support确认后的结果。
function getIdToken (code, codeVerifier) {
return new Promise((resolve, reject) => {
// prepare the request for authentication
const tokenRequest = {
redirectUri: b2cConfig.auth.redirectUri,
code: code,
codeVerifier: codeVerifier,
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
resolve(response);
}).catch((error) => {
reject(error);
});
});
}
3、解析IdToken
IdToken中包含用户的相关信息,但是没法查看,需要用到jwt-decode
库解析。
const jwtDecode = require("jwt-decode");
let tokenObj = jwtDecode(idToken);
4、获取access_token
B2C不能使用认证code获取access_token,所以采用了Azure AD免登录的方式获取了access_token。
官方参考地址: https://learn.microsoft.com/zh-cn/graph/auth-v2-service文章来源:https://www.toymoban.com/news/detail-470584.html
const confidentialClientPca = new msal.ConfidentialClientApplication({
auth: {
"clientId": SETTINGS.B2C_CLIENT_ID,
"authority": `https://login.microsoftonline.com/${SETTINGS.B2C_TENANT_ID}`,
"clientSecret": SETTINGS.B2C_SECRIT_ID,
}
});
function getClientCredentialsToken () {
return new Promise((resolve, reject) => {
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"],
azureRegion: null, // (optional) specify the region you will deploy your application to here (e.g. "westus2")
skipCache: false, // (optional) this skips the cache and forces MSAL to get a new token from Azure AD
};
confidentialClientPca.acquireTokenByClientCredential(clientCredentialRequest)
.then((response) => {
resolve(response.accessToken);
}).catch((error) => {
reject(JSON.stringify(error));
});
});
}
三、C# 操作示例
依赖包<PackageReference Include="Microsoft.Graph.Beta" Version="4.35.0-preview" />
文章来源地址https://www.toymoban.com/news/detail-470584.html
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.IO;
namespace AZ.Functuon
{
public class GraphAPI
{
public GraphServiceClient client = null;
public StreamWriter logWriter;
public string tenantName;
public GraphAPI(string tenantId,string tenantName,string clientId,string clientSecret,StreamWriter logWriter)
{
try
{
this.tenantName = tenantName;
this.logWriter = logWriter;
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
Task<AuthenticationResult> taskResult = cca.AcquireTokenForClient(scopes).ExecuteAsync();
taskResult.Wait();
InitClint(cca, scopes);
}
catch (Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
this.client = null;
}
}
public void InitClint(IConfidentialClientApplication cca,string[] scopes)
{
DelegateAuthenticationProvider authProvider = new DelegateAuthenticationProvider(async (request) =>
{
AuthenticationResult result = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
this.client = new GraphServiceClient(authProvider);
}
// ユーザのObjectIdを取得する
public async Task<string> GetObjectId(string employeeId)
{
try
{
var result = await this.client.Users
.Request()
.Filter($"identities/any(c:c/issuerAssignedId eq '{employeeId}' and c/issuer eq '{this.tenantName}@onmicrosoft.com')")
.Select(e => new
{
e.DisplayName,
e.Id,
e.Identities
})
.GetAsync();
if (result != null)
{
JObject jo = (JObject)JsonConvert.DeserializeObject(System.Text.Json.JsonSerializer.Serialize(result).Substring(1, System.Text.Json.JsonSerializer.Serialize(result).Length-2));
return jo["id"].ToString();
}
else
{
return null;
}
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return null;
}
}
// ユーザのObjectIdをtest取得する
public async Task<string> GetObjectIdByUserprincipalname(string userPrincipalName)
{
try
{
String [] userPrincipalNamefirst=userPrincipalName.Split("@");
var result = await this.client.Users[$"{userPrincipalNamefirst[0]}@{this.tenantName}.onmicrosoft.com"]
.Request(new Option[] { new QueryOption("$count", "true")})
.Header("ConsistencyLevel", "eventual")
.GetAsync();
if (result != null)
{
JObject jo = (JObject)JsonConvert.DeserializeObject(System.Text.Json.JsonSerializer.Serialize(result));
return jo["id"].ToString();
}
else
{
return null;
}
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return null;
}
}
// B2Cにユーザを新規追加する
public async Task<bool> CreateB2CUser(string employeeId, string userName,string userPrincipalName, string birthday)
{
try
{
userPrincipalName = userPrincipalName.Split("@")[0];
var user = new User
{
AccountEnabled = true,
DisplayName = userName,
MailNickname = userPrincipalName,
UserPrincipalName = $"{userPrincipalName}@{this.tenantName}.onmicrosoft.com",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = false,
Password = $"Fj_{birthday}"
},
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = "userName",
Issuer = "{this.tenantName}.onmicrosoft.com",
IssuerAssignedId = employeeId
},
}
};
var result = await this.client.Users
.Request()
.AddAsync(user);
return true;
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return false;
}
}
// B2Cユーザの認証電話番号追加
public async Task<bool> AddAuthPhoneNumber(string objectID, string phone)
{
try
{
var phoneAuthenticationMethod = new PhoneAuthenticationMethod
{
PhoneNumber = $"+81 {phone}",
PhoneType = AuthenticationPhoneType.Mobile
};
await this.client.Users[objectID].Authentication.PhoneMethods
.Request()
.AddAsync(phoneAuthenticationMethod);
return true;
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return false;
}
}
// 人事情報システム側で更新したB2Cユーザの認証電話番号を更新する
public async Task<bool> UpdateAuthPhoneNumber(string objectID, string phone)
{
try
{
var phoneAuthenticationMethod = new PhoneAuthenticationMethod
{
PhoneNumber = $"+81 {phone}",
PhoneType = AuthenticationPhoneType.Mobile
};
await this.client.Users[objectID].Authentication.PhoneMethods["3179e48a-750b-4051-897c-87b9720928f7"]
.Request()
.PutAsync(phoneAuthenticationMethod);
return true;
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return false;
}
}
// 明細照会サービス側で追加したB2CユーザのユーザIDを更新する
public async Task<bool> UpdateUserId(string objectID, string userId, string singleName)
{
try
{
var user = new User
{
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = "userName",
Issuer = "{this.tenantName}.onmicrosoft.com",
IssuerAssignedId = userId
},
},
};
await this.client.Users[objectID]
.Request()
.UpdateAsync(user);
return true;
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return false;
}
}
// B2Cにユーザを削除する
public async Task<bool> DeleteUserByObjId(string objectId)
{
try
{
await this.client.Users[objectId]
.Request()
.DeleteAsync();
return true;
}
catch(Exception e)
{
CommonFunction.WriteLog(logWriter, "ERROR", $"{e.Message} | {e.StackTrace}");
return false;
}
}
}
}
到了这里,关于Azure AD认证和Azure AD B2C的token获取的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!