[ASP.NET][IIS]別アプリケーション＆別サーバでセッションを共有する方法

IIS のセッション保存は、インプロセス(デフォルト)、SQL Server、ASP.NET 状態サービス(StateServer)が可能。
インプロセスではサーバー間でセッションは共有できないため、ここでは比較的導入が簡単なStateServerでの方法を記す。

StateServerでセッションを共有する場合、アプリケーション名、machineKey が一致＆ サイト間で同じCookiesを利用していればよい。
アプリケーション名はWeb.configだけで設定できないので、起動時に設定するように処理を追加する必要がある。
アプリケーション名が一致しない場合、セッションIDだけを共有でき、セッションデータは共有できない。

## Web.configの設定

``` Web.config
<configuration>
  <system.web>
    <!-- Cookieを共有するサイトのドメインを指定-->
    <httpCookies domain=".your.site"/>
    <!-- セッション StateServerにセッション保存、セッションクッキー名の指定、タイムアウト指定-->
    <sessionState cookieless="UseCookies" cookieName="sessionXXXX"　mode="StateServer" regenerateExpiredSessionId="false" stateConnectionString="tcpip=10.1.1.1:42424" timeout="30">
      <providers>
        <clear />
      </providers>
    </sessionState>
    <!-- machineKeyを設定-->
    <machineKey validationKey="B0577D3D82E341C0464C090C68681EC661DDB684E3B23166C7E8DE526490A5847152EE9EAA2928B89409FB4EB97F13BA12D11E6FABB4B8E3923364B496BA50FC" decryptionKey="93F24EC212137CC181B543FB8329C71810C351F76B0A2090CB9887811AC9DCC6" validation="SHA1" decryption="AES" /> 
  </system.web>
  <appSettings>
    <!-- アプリケーション名を設定-->
    <add key="ApplicationName" value="appName" />
  </appSettings>

</configuration>
```

- Cookieを共有するサイトのドメインを指定。ドメインを広げすぎるとセキュリティ的に良くないので注意。  
ちゃんとやるなら、IIS ARRなどでリバースプロキシを用意して実施する。（リバースプロキシだと、1つのサーバにアクセスすればよいので、ドメイン指定しなくても同じCookieが使われる。）
- セッション保存はsessionState 句にてStateServerを使うように指定。  
ASP.NET 状態サービス(StateServer)のデフォルトポートは42424.   
セッションクッキー名は、デフォルトのままでも良いが、ドメイン内で重複して他アプリへの影響も考慮し、設定。
- MachineKeyは簡単に作れる Web サービスが公開されているので利用  
*ASP.Net MachineKey Generator*  
http://www.allkeysgenerator.com/Random/ASP-Net-MachineKey-Generator.aspx
- アプリケーション名を指定。  
後ほど処理記述にて、このアプリケーション名をセットする。

## ASP.NET 状態サービス(StateServer)の設定＆起動

- State Server のファイアウォールで TCP の 42424 ポートを許可
- State Server のレジストリを修正 `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters` の `AllowRemoteConnection` を `1` に
- 「ASP.NET 状態サービス」を無効=>自動に、起動。   
「ASP.NET 状態サービス」がレジストリ修正時に起動していたら再起動。

``` ConfigureStateServerRemote.cmd
Rem State Server のファイアウォールで TCP の 42424 ポートを許可
netsh advfirewall firewall add rule name="ASP .NET Session State Server" dir=in action=allow protocol=TCP localport=42424

Rem 現在の値を確認
Reg Query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters /v AllowRemoteConnection
Rem State Server のレジストリを修正 
Rem `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters` の `AllowRemoteConnection` を `1` に変更
Reg Add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters /v AllowRemoteConnection /t REG_DWORD /d 1 /f

Rem 現在の値を確認
Reg Query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters /v AllowRemoteConnection


Rem 「ASP.NET 状態サービス」を無効=>自動に
sc.exe config "aspnet_state" start=auto
sc stop "aspnet_state"
sc start "aspnet_state"

```


# Global.asaxにてアプリケーション名を指定

``` Global.asax
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;

    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            ConfigureSession();
        }

        private void ConfigureSession()
        {
            // Get the app name from config file...
            string appName = System.Configuration.ConfigurationManager.AppSettings["ApplicationName"];
            if (!string.IsNullOrEmpty(appName))
            {
                foreach (string moduleName in this.Modules)
                {
                    IHttpModule module = this.Modules[moduleName];

                    SessionStateModule ssm = module as SessionStateModule;
                    if (ssm != null)
                    {
                        FieldInfo storeInfo = typeof(SessionStateModule).GetField("_store", BindingFlags.Instance | BindingFlags.NonPublic);
                        SessionStateStoreProviderBase store = (SessionStateStoreProviderBase)storeInfo.GetValue(ssm);
                        if (store == null) //In IIS7 Integrated mode, module.Init() is called later
                        {
                            FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.Static | BindingFlags.NonPublic);
                            HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null);
                            FieldInfo appNameInfo = typeof(HttpRuntime).GetField("_appDomainAppId", BindingFlags.Instance | BindingFlags.NonPublic);
                            appNameInfo.SetValue(theRuntime, appName);
                        }
                        else
                        {
                            Type storeType = store.GetType();
                            if (storeType.Name == "OutOfProcSessionStateStore")
                            {
                                FieldInfo uribaseInfo = storeType.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic);
                                uribaseInfo.SetValue(storeType, appName);
                            }
                        }
                    }
                }
            }
        }
    }

```

## 参照元

- Sharing session between two asp.net web applications  
http://www.freshcodehub.com/Article/48/sharing-session-between-two-aspnet-web-applications
- Share Session between two web sites using asp.net and state server  
https://stackoverflow.com/questions/3438912/share-session-between-two-web-sites-using-asp-net-and-state-server
- リモートの State Server に接続するためのメモ
http://kendik.hatenablog.com/entry/2013/05/16/002108