うちの社内アプリの認証方法は今までバラバラで、メンテナンスとか非常に大変だったので、今後はAzure ADに統一していきたいなと思っている今日この頃。
とりあえず、Microsoft Graph APIを使用して、認証とアクセス権チェック(指定グループに属しているかどうかで判断する)を試してみた。
dotnet core 3.1コンソールアプリで実装。
まず、プロジェクトにMicrosoft.Graph,Microsoft.Graph.Auth,Microsoft.Identity.Clientパッケージを追加する。
Webアプリではないので、今回は前回と同様、ユーザ名・パスワード指定でTokenを取得する。(実際の取得はGraph API内部で行う)
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(ApplicationID)
.WithAuthority(Authority)
.Build();
// Scopeはユーザ情報の読取とグループ情報の読取があればいいかな
string[] scopes = new string[] { "https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Group.Read.All" };
// ユーザ名・パスワードでアクセスするので、Microsoft.Graph.Auth.UserNamePasswordProviderを使用する
var provider = new UsernamePasswordProvider(app,scopes);
// Microsoft.GraphGraphServiceClientを作成
var graphClient = new GraphServiceClient(provider);
// ログインユーザの情報を取得する
var me = await graphClient.Me.Request()
.WithUsernamePassword(username,secPwd)
.GetAsync();
// ログインユーザが属している、グループを取得する
var members = await graphClient.Me.MemberOf.Request().GetAsync();
if (members != null) {
foreach(var m in members) {
if (m.GetType() == typeof(Group)) {
Group g = (Group)m;
Console.WriteLine($"{g.Id} : {g.DisplayName}");
}
}
}
MemberOfは自分が属しているグループの一覧なので、このグループが他のグループに属している場合、そのグループに属していることはこの一覧からは確認できない。
つまり、以下のような状態で、自分が最終的にAグループに属しているかどうかはこの一覧からは分からないということ。MemberOfで取得されるのは、BグループとCグループとなるからだ。
こういうグループの入れ子は配布リストなどには良く見られるよね。セキュリティグループはこういう設計をしない方が良いとは思うけど、場合によりけりだろうか・・・
で、自分がAグループに属しているかどうかを調べるには、グループを手繰っていく必要がある。効率はともかくとして、とりあえず以下のようなメソッドを作ってみた。
// グループを手繰って、指定グループに属しているかどうか調べる
// 最初に指定するグループが指定グループ
static async Task<bool> IsMember(Group g,string mail,GraphServiceClient graphClient,string username, SecureString secPwd) {
foreach(var m in g.Members) {
if (m.GetType() == typeof(User)) { // メンバーがユーザならメールアドレスをチェック
User u = (User)m;
if (u.Mail == mail) {
return true;
}
} else if (m.GetType() == typeof(Group)) { // グループならグループ内をチェック
Group sg = (Group)m;
var grps = await graphClient.Groups.Request()
.WithUsernamePassword(username,secPwd)
.Expand("Members") // これを入れないと、Membersが取れない
.Filter($"id eq '{sg.Id}'") // IDを指定
.GetAsync();
var grp = grps.FirstOrDefault();
if (grp != null) {
// 再帰呼出し
return await IsMember(grp,mail,graphClient,username,secPwd);
} else {
return false;
}
}
}
return false;
}
グループ深度が深くなれば、何回もhttpリクエストが発生するので、効率的ではないとは思うけど・・・
まぁ、結果はOKでした。