graph_authentication_example
这是控制台应用程序基于令牌的身份验证示例。应用程序必须至少运行一次,系统会提示您登录,但一旦完成,身份验证令牌就会存储在运行应用程序的机器上。
我从我们的服务器运行控制台应用程序作为任务并访问 Graph API 以使用 Graph Endpoints 获取各种 ActiveDirectory 数据集 - 通常我需要在发布后登录,然后再运行 - 这只是在测试阶段现在但似乎运作良好。
依赖关系:
- 必须有一个 Azure Active Directory 用户,用于登录和后续身份验证。一切都发生在这个用户的上下文中。
- 使用以下 Nuget 包:
- Microsoft.Graph >= v1.6.2
- Microsoft.Graph.Core >= v1.6.2
- Microsoft.Identity.Client >= v1.1.0 预览版
- Microsoft.IdentityModel.Clients.ActiveDirectory >= v3.17.1
- Newtonsoft.Json >= v1.0.3
- System.Net.Http >= v4.3.3
- System.Security.Cryptography.Algorithms >= v4.3.0
- System.Security.Cryptography.Encoding >= v4.3.0
- System.Security.Cryptography.Primitives >= v4.3.0
- 您必须在此处创建一个应用程序 [https://apps.dev.microsoft.com/] 在上面创建的同一用户下,这将为您提供您的客户端/应用程序 ID。
- 您可以在此处查看 Graph [https://developer.microsoft.com/en-us/graph/graph-explorer/] 并使用上面创建的用户登录以针对您自己的 Azure Active Directory 进行测试。
设置
我使用 .ini 文件来存储设置,但值是有效的,.ini 样式的 cmets - 请注意,某些条目中有 {name} 样式文本,用于字符串替换。
您将看到 Settings.SomeName - 映射到以下内容:
[Endpoint]
; we don't want v1.0 because of our needs but it is valid
GraphVersion = beta ; v1.0 or beta
; Common Graph endpoint - we sub version
GraphEndpoint = https://graph.microsoft.com/{version}
[Auth]
; authentication uri
Uri = https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
; authority uri
Authority = https://login.microsoftonline.com/{tenant}
; if we need to login or re-login
RedirectUri = https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient
; you may have a GUID style tenant but 'common' worked fine since it auth's back to Azure anyway
Tenant = common
; the scopes we needed with Graph, yours may vary
Scopes = { User.ReadBasic.All, User.Read.All, User.ReadWrite.All, Directory.AccessAsUser.All, Directory.Read.All, Directory.ReadWrite.All, Group.ReadWrite.All }
; the id of your azure application - guid
ClientId = xxxx###-2##8-4##9-b##1-ec#########8f2
GrantType = client_credentials
代码片段
我试图包含完整的函数并通过指出它们来自哪些文件来表示分离。这些是令牌身份验证的主要部分 - 大部分是完整的代码,但“私有”代码除外。
起点:假设在我的 Program.cs 中,我调用了以下函数,一切都从这里流出以进行身份验证:
// we are just getting a group by id - CreateAuthenticatedClient() is called before every call to Graph
public static async Task<Group> GetGroupAsync(string groupId)
{
var graphClient = AuthenticationHelper.CreateAuthenticatedClient();
try
{
var group = await graphClient.Groups[groupId].Request().GetAsync();
if (group == null) return null;
return group;
}
catch (ServiceException e)
{
ConsoleHelper.WriteException($"GetGroupAsync.{ServiceErrorString(e)}");
return null;
}
}
AuthenticationHelper.cs - 完整类
public class AuthenticationHelper
{
public static string TokenForUser = null;
public static DateTimeOffset TokenForUserExpiration;
// this is the 'magic' where we get the user cache
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(Settings.AuthClientId, Settings.AuthAuthority, TokenCacheHelper.GetUserCache());
private static GraphServiceClient graphClient = null;
// Get an access token for the given context and resourceId. An attempt is first made to
// acquire the token silently. If that fails, then we try to acquire the token by prompting the user.
public static GraphServiceClient CreateAuthenticatedClient()
{
if (graphClient == null)
{
try
{
graphClient = new GraphServiceClient(
Settings.GraphEndpoint,
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetTokenForUserAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
requestMessage.Headers.Add("azure-graph-test", "manage group membership");
}));
return graphClient;
}
catch (ServiceException sex)
{
Console.WriteLine($"Could not create a graph client service: {sex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Could not create a graph client: {ex.Message}");
}
}
return graphClient;
}
/// <summary>
/// get Token for User
/// </summary>
public static async Task<string> GetTokenForUserAsync()
{
if (TokenForUser == null || TokenForUserExpiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
AuthenticationResult authResult;
try
{
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Settings.AuthScopes, IdentityClientApp.Users.FirstOrDefault());
TokenForUser = authResult.AccessToken;
}
catch (Exception)
{
if (TokenForUser == null || TokenForUserExpiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Settings.AuthScopes);
TokenForUser = authResult.AccessToken;
TokenForUserExpiration = authResult.ExpiresOn;
}
}
}
return TokenForUser;
}
}
TokenCacheHelper.cs - 这是一个 microsoft 类,它是获取/设置令牌缓存的关键
// Copyright (c) Microsoft Corporation.
// All rights reserved.
// This code is licensed under the MIT License.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
static class TokenCacheHelper
{
/// <summary>
/// Get the user token cache
/// </summary>
public static TokenCache GetUserCache()
{
if (usertokenCache == null)
{
usertokenCache = new TokenCache();
usertokenCache.SetBeforeAccess(BeforeAccessNotification);
usertokenCache.SetAfterAccess(AfterAccessNotification);
}
return usertokenCache;
}
static TokenCache usertokenCache;
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt";
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.Deserialize(File.Exists(CacheFilePath)
? File.ReadAllBytes(CacheFilePath)
: null);
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.TokenCache.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
File.WriteAllBytes(CacheFilePath, args.TokenCache.Serialize());
// once the write operationtakes place restore the HasStateChanged bit to filse
args.TokenCache.HasStateChanged = false;
}
}
}
}