0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python Flask のアプリへ Windows 認証ユーザを連携したい

Posted at

はじめに

前回の検証中に、 IIS で Windows 認証を有効にして Python アプリに認証ユーザを引き渡せたら今後色々と楽でいいのに、と思ったので、調査・検証してみました。

対応方法(概要)

HttpPlatformHandler v1.2 では、Windows 認証トークンを HTTP Header で受け取れるとのことで、その値を Python.NET を活用して処理するようにします。
(下記表最終行に、forwardWindowsAuthToken=true にすることで、HTTP Header として X-IIS-WindowsAuthToken が得られる、CloseHandle は受け取った側で責任持って実施すること、との記載あり)

開発環境

Visual Studio 2022 で .NET Framework 向けに開発した方が良かったのですが、事情により、Visual Studio Code + .NET 8 です。

[インストール物]
Visual Studio Code
.NET 8 SDK
VC# Dev Kit (VS Code Extension)

事前調査

Windows 認証機能はオプションなので、未インストールであればサーバーマネージャーからインストールしておく。

image.png

対象サイトに対して Windows 認証を有効に、匿名認証を無効にする。

image.png

前回開発物のログを強化し、HTTP Header を出力するようにしてみた。

python_iis_w.py
from flask import Flask, request

+ import logging
+ logger = logging.getLogger(__name__)
+ logger.setLevel(logging.DEBUG)
+ fh = logging.FileHandler("C:\\home\\LogFiles\\test.log")
+ fh.setLevel(logging.DEBUG)
+ fh.setFormatter(logging.Formatter("%(asctime)s : %(message)s"))
+ logger.addHandler(fh)

app = Flask(__name__)

@app.route("/")
def hello_world():
  + logger.debug("----------")
  + logger.debug(request.headers)
    return "<p>Hello World! with Waitress1</p>"

if __name__ == '__main__':
    app.run(debug=True)

Web.config で当機能をONにして動作確認。

Web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="PythonHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
    </handlers>
    <httpPlatform processPath="C:\WebSites\HelloWorldPyW1\venv\Scripts\python.exe"
                  arguments="waitress_server.py %HTTP_PLATFORM_PORT%"
                  stdoutLogEnabled="true"
                  stdoutLogFile="c:\home\LogFiles\pythong.log"
                  startupTimeLimit="60"
                  processesPerApplication="16"
                + forwardWindowsAuthToken="true" >
      <environmentVariables>
      </environmentVariables>
    </httpPlatform>
  </system.webServer>
</configuration>

連携されている↓

image.png

が、これが認証トークンへのハンドルであり、CloseHandle しろということなので、いきなりアンマネージリソースをリークさせてます(笑) よろしくないです。

開発(DLL)

空のフォルダ(C:\PythonPJ\GetUserInfoCS とした)を用意して、HTTP ヘッダの値をパラメータとして受け取り、ユーザー名を返却する C# DLL を .NET 8 で開発する。

cd C:\PythonPJ\GetUserInfoCS
dotnet new classlib

参考ソースは下記、依存モジュールを減らしたかったので適宜調整。

TokenConverter.cs
using System;
using System.Security.Claims;
using System.Globalization;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace GetUserInfoCS {

    public class TokenConverter
    {
        internal static class NativeMethods
        {
            [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
            public static extern bool CloseHandle(IntPtr handle);
        }

        public string GetUser(string xtoken)
        {
            var ret = "";

            var tokenHeader = xtoken;

            int hexHandle;
            if (!string.IsNullOrEmpty(tokenHeader)
                && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
            {
                // Always create the identity if the handle exists, we need to dispose it so it does not leak.
                var handle = new IntPtr(hexHandle);
                using (WindowsIdentity winIdentity = new WindowsIdentity(handle))
                {
                    ret = winIdentity.Name;
                    NativeMethods.CloseHandle(handle);
                }
            }

            return ret;
        }

    }
}

ビルドすると、マルチプラットフォーム対応の .NET なのに Windows 固有の実装をしている旨、警告がでるが無視する。

image.png

できたファイル(GetUserInfoCS.dll)は python_iis_w.py と同じパスにコピーする。

Python ソースへの組み込み

Python.NET を組み込んで改訂する。

.\env\Scripts\Activate
pip install pythonnet

毎回確実にリソースリークしないよう対応する必要があり、Flask の before_request で処理するのがよさそう。

python_iis_w.py
from flask import Flask, request

+ from pythonnet import load
+ load('coreclr')
+ import clr
+ clr.AddReference('GetUserInfoCS')
+ from GetUserInfoCS import TokenConverter

import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("C:\\home\\LogFiles\\test.log")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s : %(message)s"))
logger.addHandler(fh)

app = Flask(__name__)

+ @app.before_request
+ def before_request():
+     logger.debug("----------")
+     logger.info(request.headers)
+     xtoken = request.headers.get("X-IIS-WindowsAuthToken")
+     if xtoken:
+         tc = TokenConverter()
+         uid = tc.GetUser(xtoken)
+         logger.info(uid)

@app.route("/")
def hello_world():
-   logger.debug("----------")
-   logger.info(request.headers)
    return "<p>Hello World! with Waitress1</p>"

if __name__ == '__main__':
    app.run(debug=True)

動作確認

image.png

ドメインID + "\" + ユーザID 形式で取得できたので、ログインユーザをもとにアプリ内の権限レベルをチェックする等、好きにできそうです。

最後に

[参考にさせていただいた主情報]
ほぼこちらの記事が答えでした、ありがとうございます。
https://blog.shibayan.jp/entry/20151029/1446126178

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?