增加在 IIS 上運行的 ASP.NET Core 應用程式的工作階段超時

更新頁 :
頁面創建日期 :

操作驗證環境

Visual 工作室
  • Visual Studio 2022
ASP.NET 核心
  • 8 (Razor Pages, MVC)
Windows 伺服器
  • 2022
IIS的
  • 10.0

操作環境

我們尚未在所有情況下對其進行測試,但它應該在以下條件下正常工作:

Visual 工作室
  • 任何可以開發 ASP.NET 核心項目的東西
ASP.NET 核心
  • 任何版本(MVC、Razor Pages、API)
Windows 伺服器
  • Windows Server 2008 或更高版本
IIS的
  • 7.0 或更高版本

起先

使用 ASP.NET Core 編寫的應用程式運行在一個名為“Kestrel”的簡單伺服器上,會話超時等持續時間主要以程式設計方式設置。

但是,如果在 IIS 上託管並運行 ASP.NET Core 應用程式,則可能需要重寫 IIS 中設置的值,而不是程式設計設置。

由於這次描述的空閒時間導致的會話超時受 IIS 設置的影響,因此讓我們設置此設置。 但是,請注意,會話時間受多個設置的影響,因此根據設置的不同,會話超時可能無法僅使用此設置來延長。

這是通過測試程序解釋的,但如果您想要快速設置它,請跳到本頁末尾。

登錄程式

這一次,我們將檢查登錄會話中的操作。 您可以使用其他會話,但請注意會話的預設超時。

對於登錄程式,幾乎總是使用以下提示,因此有關詳細資訊,請參閱以下內容。 不管是 Razor Pages 還是 MVC。

此提示僅涉及會話超時,因此我不會深入探討代碼的其餘部分。 修改命名空間以匹配您的專案。

Razor Pages 項目代碼

Program.cs

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// コンテナにサービスを追加します。
builder.Services.AddRazorPages();

// ※ここから追加

// Cookie による認証スキームを追加する
builder.Services
  .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie();

builder.Services.AddAuthorization(options =>
{
  // AllowAnonymous 属性が指定されていないすべての画面、アクションなどに対してユーザー認証が必要となる
  options.FallbackPolicy = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();
});

// ※ここまで追加

var app = builder.Build();

// HTTP リクエスト パイプラインを構成します。
if (!app.Environment.IsDevelopment())
{
  app.UseExceptionHandler("/Error");
  // デフォルトの HSTS 値は 30 日です。 運用シナリオではこれを変更することもできます。https://aka.ms/aspnetcore-hsts を参照してください。
  app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); // [追加] 認証
app.UseAuthorization(); // 認可

app.MapRazorPages();

app.Run();

頁數/帳戶/Login.cshtml.cs

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;

namespace SessionTimeoutExtensionIisRazorPages.Pages
{
  [AllowAnonymous]
  public class LoginModel : PageModel
  {
    /// <summary>ユーザー名。</summary>
    [BindProperty]
    [Required]
    [DisplayName("ユーザー名")]
    public string UserName { get; set; } = "";

    /// <summary>パスワード。</summary>
    [BindProperty]
    [Required]
    [DataType(DataType.Password)]
    [DisplayName("パスワード")]
    public string Password { get; set; } = "";

    /// <summary>仮のユーザーデータベースとする。</summary>
    private Dictionary<string, string> UserAccounts { get; set; } = new Dictionary<string, string>
    {
      { "user1", "password1" },
      { "user2", "password2" },
    };

    /// <summary>ログイン処理。</summary>
    public async Task<ActionResult> OnPost()
    {
      // 入力内容にエラーがある場合は処理を中断してエラー表示
      if (ModelState.IsValid == false) return Page();

      // ユーザーの存在チェックとパスワードチェック (仮実装)
      // 本 Tips は Cookie 認証ができるかどうかの確認であるため入力内容やパスワードの厳密なチェックは行っていません
      if (UserAccounts.TryGetValue(UserName, out string? getPass) == false || Password != getPass)
      {
        ModelState.AddModelError("", "ユーザー名またはパスワードが一致しません。");
        return Page();
      }

      // サインインに必要なプリンシパルを作る
      var claims = new[] { new Claim(ClaimTypes.Name, UserName) };
      var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
      var principal = new ClaimsPrincipal(identity);

      // 認証クッキーをレスポンスに追加
      await HttpContext.SignInAsync(principal);

      // ログインが必要な画面にリダイレクトします
      return RedirectToPage("/Index");
    }

    /// <summary>ログアウト処理。</summary>
    public async Task OnGetLogout()
    {
      // 認証クッキーをレスポンスから削除
      await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
  }
}

Pages/Account/Login.cshtml

@page
@model LoginModel
@{}

<form asp-action="Login">
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    </div>
  </div>

  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <label asp-for="UserName" class="form-label"></label>
      <input asp-for="UserName" class="form-control" value="user1" />
      <span asp-validation-for="UserName" class="text-danger"></span>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <label asp-for="Password" class="form-label"></label>
      <input asp-for="Password" class="form-control" value="password1" />
      <span asp-validation-for="Password" class="text-danger"></span>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <button type="submit" class="btn btn-primary">ログイン</button>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <a asp-page="/Index">認証が必要な画面へ直接リンク</a>
    </div>
  </div>
</form>

@section Scripts {
  @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

Pages/Index.cshtml

@page
@model IndexModel
@{
  ViewData["Title"] = "Home page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<p>アクセス日時:@DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")</p>

Pages/Shared/_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>@ViewData["Title"] - SessionTimeoutExtensionIisRazorPages</title>
  <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
  <link rel="stylesheet" href="~/SessionTimeoutExtensionIisRazorPages.styles.css" asp-append-version="true" />
</head>
<body>
  <header>
    <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
      <div class="container">
        <a class="navbar-brand" asp-area="" asp-page="/Index">SessionTimeoutExtensionIisRazorPages</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
          <ul class="navbar-nav flex-grow-1">
            <li class="nav-item">
              <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
            </li>
            @* ここから追加 *@
            <li class="nav-item">
              <a class="nav-link text-dark" asp-page="/Account/Login" asp-page-handler="Logout">ログアウト</a>
            </li>
            <li class="nav-item">
              <a class="nav-link text-dark" asp-page="/Account/Login">ログアウトせずログインへ</a>
            </li>
            @* ここまで追加 *@
          </ul>
        </div>
      </div>
    </nav>
  </header>
  <div class="container">
    <main role="main" class="pb-3">
      @RenderBody()
    </main>
  </div>

  <footer class="border-top footer text-muted">
    <div class="container">
      &copy; 2024 - SessionTimeoutExtensionIisRazorPages - <a asp-area="" asp-page="/Privacy">Privacy</a>
    </div>
  </footer>

  <script src="~/lib/jquery/dist/jquery.min.js"></script>
  <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
  <script src="~/js/site.js" asp-append-version="true"></script>

  @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

MVC 項目代碼

Program.cs

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// コンテナにサービスを追加します。
builder.Services.AddControllersWithViews();

// ※ここから追加

// Cookie による認証スキームを追加する
builder.Services
  .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie();

builder.Services.AddAuthorization(options =>
{
  // AllowAnonymous 属性が指定されていないすべての画面、アクションなどに対してユーザー認証が必要となる
  options.FallbackPolicy = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();
});

// ※ここまで追加

var app = builder.Build();

// HTTP リクエスト パイプラインを構成します。
if (!app.Environment.IsDevelopment())
{
  app.UseExceptionHandler("/Home/Error");
  // デフォルトの HSTS 値は 30 日です。 運用シナリオではこれを変更することもできます。https://aka.ms/aspnetcore-hsts を参照してください。
  app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); // [追加] 認証
app.UseAuthorization(); // 認可

app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

型號/LoginModel.cs

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SessionTimeoutExtensionIisMvc.Models
{
  public class LoginModel
  {
    /// <summary>ユーザー名。</summary>
    [Required]
    [DisplayName("ユーザー名")]
    public string UserName { get; set; } = "";

    /// <summary>パスワード。</summary>
    [Required]
    [DataType(DataType.Password)]
    [DisplayName("パスワード")]
    public string Password { get; set; } = "";
  }
}

控制器/AccountController.cs

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SessionTimeoutExtensionIisMvc.Models;
using System.Security.Claims;

namespace SessionTimeoutExtensionIisMvc.Controllers
{
  /// <remarks>
  /// <see cref="AllowAnonymous"/> 属性は Cookie 認証していなくてもアクセスできる Action (Controller) であることを示す。
  /// </remarks>
  [AllowAnonymous]
  public class AccountController : Controller
  {
    /// <summary>仮のユーザーデータベースとする。</summary>
    private Dictionary<string, string> UserAccounts { get; set; } = new Dictionary<string, string>
    {
      { "user1", "password1" },
      { "user2", "password2" },
    };

    /// <summary>ログイン画面を表示します。</summary>
    public IActionResult Login() => View();

    /// <summary>ログイン処理を実行します。</summary>
    [HttpPost]
    public async Task<IActionResult> Login(LoginModel model)
    {
      // 入力内容にエラーがある場合は処理を中断してエラー表示
      if (ModelState.IsValid == false) return View(model);

      // ユーザーの存在チェックとパスワードチェック (仮実装)
      // 本 Tips は Cookie 認証ができるかどうかの確認であるため入力内容やパスワードの厳密なチェックは行っていません
      if (UserAccounts.TryGetValue(model.UserName, out string? getPass) == false || model.Password != getPass)
      {
        ModelState.AddModelError("", "ユーザー名またはパスワードが一致しません。");
        return View(model);
      }

      // サインインに必要なプリンシパルを作る
      var claims = new[] { new Claim(ClaimTypes.Name, model.UserName) };
      var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
      var principal = new ClaimsPrincipal(identity);

      // 認証クッキーをレスポンスに追加
      await HttpContext.SignInAsync(principal);

      // ログインが必要な画面にリダイレクトします
      return RedirectToAction(nameof(HomeController.Index), "Home");
    }

    /// <summary>ログアウト処理を実行します。</summary>
    public async Task<IActionResult> Logout()
    {
      // 認証クッキーをレスポンスから削除
      await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

      // ログイン画面にリダイレクト
      return RedirectToAction(nameof(Login));
    }
  }
}

視圖/帳戶/登錄名.cshtml

@model LoginModel
@{}

<form asp-action="Login">
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    </div>
  </div>

  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <label asp-for="UserName" class="form-label"></label>
      <input asp-for="UserName" class="form-control" value="user1" />
      <span asp-validation-for="UserName" class="text-danger"></span>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <label asp-for="Password" class="form-label"></label>
      <input asp-for="Password" class="form-control" value="password1" />
      <span asp-validation-for="Password" class="text-danger"></span>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <button type="submit" class="btn btn-primary">ログイン</button>
    </div>
  </div>
  <div class="row m-1 g-3">
    <div class="col-sm-6 offset-sm-3">
      <a asp-controller="Home" asp-action="Index">認証が必要な画面へ直接リンク</a>
    </div>
  </div>
</form>

@section Scripts {
  @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

視圖/主頁/索引.cshtml

@{
  ViewData["Title"] = "Home Page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<p>アクセス日時:@DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")</p>

視圖/共用/_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>@ViewData["Title"] - SessionTimeoutExtensionIisMvc</title>
  <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
  <link rel="stylesheet" href="~/SessionTimeoutExtensionIisMvc.styles.css" asp-append-version="true" />
</head>
<body>
  <header>
    <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
      <div class="container-fluid">
        <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">SessionTimeoutExtensionIisMvc</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
          <ul class="navbar-nav flex-grow-1">
            <li class="nav-item">
              <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
            </li>
            @* ここから追加 *@
            <li class="nav-item">
              <a class="nav-link text-dark" asp-controller="Account" asp-action="Logout">ログアウト</a>
            </li>
            <li class="nav-item">
              <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">ログアウトせずログインへ</a>
            </li>
            @* ここまで追加 *@
          </ul>
        </div>
      </div>
    </nav>
  </header>
  <div class="container">
    <main role="main" class="pb-3">
      @RenderBody()
    </main>
  </div>

  <footer class="border-top footer text-muted">
    <div class="container">
      &copy; 2024 - SessionTimeoutExtensionIisMvc - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
    </div>
  </footer>
  <script src="~/lib/jquery/dist/jquery.min.js"></script>
  <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
  <script src="~/js/site.js" asp-append-version="true"></script>
  @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

通過調試檢查操作

檢查登錄行為,並確保在未登錄的情況下無法訪問其他頁面。 本地調試時的超時時間與在 IIS 上運行時的超時時間不同,因此調試時無需檢查超時。

將程式部署到 IIS

設置 IIS 並部署您建立的程式。 此處不介紹設置 IIS,因為它是多餘的。 有關詳細說明,請參閱下面的提示和其他資訊。

檢查部署在 IIS 上的應用程式的工作階段超時

到目前為止,程式和 IIS 都沒有設置會話超時,因此一切都應該是預設的。 首先,登錄並保留至少 20 分鐘,然後 Index 嘗試訪問 and Privacy 頁面。 您最多可以訪問它 20 分鐘,但如果它超時,您應該被帶到登錄頁面。

延長 IIS 管理員中的超時期限

如果尚未在程式中設置超時,則應該能夠在應用程式池設置中延長超時時間。

啟動 IIS 管理員時,從左側功能表中選擇「應用程式池」。。 選擇用於操作網站的應用程式池,然後從右側功能表中選擇「高級設置」。

在「流程模型」組中,有一個名為「空閒超時(分鐘)」的專案,因此請將其設置為以分鐘為單位的任意時間量。

設置完成後,用OK按鈕確認。

之後,請檢查會話超時的操作。 如果將其設置為 60 分鐘,即使頁面在登錄 50 分鐘後轉換,如果您未登出,也會成功。

雖然有些人可能希望儘可能延長會話超時之間的時間,但可以設置的最長值為 29 小時。 這與回收應用程式池的時間相對應。 還可以通過延長應用程式池的回收時間來延長超時時間。 還有一個問題是,使用的記憶體會繼續保留,所以如果你改變它,請根據操作進行調整。

此外,即使您在超時前設置了大量時間,它也經常在此之前超時。 這是因為應用程式池在超時到期之前被回收,或者如果一段時間未被訪問,它會進入暫停狀態。 如果要確保會話在指定的時間內保持,則需要採取其他措施。