Auth

TsgcHTMLAuth:为 sgcHTML 应用提供服务器端会话、登录状态和 CSRF 防护,适用于 Delphi、C++ Builder 和 .NET。

TsgcHTMLAuth

一个非可视组件,负责每个请求的会话 cookie、已登录用户和 CSRF 令牌。指定一个会话存储,处理 OnAuthenticate 来检查密码,设置 HTMX 引擎的 Auth 属性,并用 RequireLogin 标记路由。它是 sgcHTML 的一部分,后者作为独立的单独包销售。

组件类

TsgcHTMLAuth,以及 TsgcHTMLSessionStore_MemoryTsgcHTMLSessionStore_FileTsgcHTMLSession(单元 sgcHTML_AuthsgcHTML_Session

渲染为

无标记:会话 cookie、CSRF 令牌和登录重定向

家族

身份验证

语言

Delphi, C++ Builder, .NET

创建存储、创建 Auth、保护路由

指定一个会话存储,处理 OnAuthenticate,在引擎上设置 Auth,并标记需要保护的路由。此后,引擎会读取每个请求的会话,检查 CSRF 令牌,并把匿名用户重定向到 LoginPath

uses
  sgcHTML_Session, sgcHTML_Auth, sgcHTMX_Router, sgcHTMX_Engine_Server;

// oHTMX is a TsgcHTMX_Engine_Server, oRouter is a TsgcHTMX_Router
var
  oStore: TsgcHTMLSessionStore_Memory;
  oAuth: TsgcHTMLAuth;
  oRoute: TsgcHTMX_Route;
begin
  oStore := TsgcHTMLSessionStore_Memory.Create(Self);
  oStore.SweepInterval := 60;

  oAuth := TsgcHTMLAuth.Create(Self);
  oAuth.SessionStore := oStore;
  oAuth.IdleTimeout := 1800;       // seconds without a request
  oAuth.AbsoluteTimeout := 43200;  // seconds since sign-in
  oAuth.LoginRateLimit.MaxAttempts := 5;
  oAuth.OnAuthenticate := AuthAuthenticate;
  oHTMX.Auth := oAuth;

  // only a signed-in user reaches /orders
  oRoute := oRouter.Routes.Add;
  oRoute.Path := '/orders';
  oRoute.RequireLogin := True;
  oRoute.OnRoute := OrdersRoute;

  // only a session with one of these roles reaches /admin
  oRoute := oRouter.Routes.Add;
  oRoute.Path := '/admin';
  oRoute.RequireRoles := 'admin,manager';
  oRoute.OnRoute := AdminRoute;
end;

procedure TForm1.AuthAuthenticate(Sender: TObject;
  const aUser, aPassword: string; var aAccept: Boolean;
  var aUserID, aDisplayName: string; const aRoles: TStrings);
begin
  // LoadPasswordHash reads the value stored when the user was created
  aAccept := sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
  if aAccept then
  begin
    aUserID := aUser;
    aDisplayName := aUser;
    aRoles.Add('admin');
  end;
end;

// when you create a user, store this hash, never the password
vHash := sgcHTMLPasswordHash('secret');
// includes: sgcHTML_Session.hpp, sgcHTML_Auth.hpp, sgcHTMX_Router.hpp, sgcHTMX_Engine_Server.hpp
// oHTMX is a TsgcHTMX_Engine_Server, oRouter is a TsgcHTMX_Router

TsgcHTMLSessionStore_Memory *oStore = new TsgcHTMLSessionStore_Memory(this);
oStore->SweepInterval = 60;

TsgcHTMLAuth *oAuth = new TsgcHTMLAuth(this);
oAuth->SessionStore = oStore;
oAuth->IdleTimeout = 1800;       // seconds without a request
oAuth->AbsoluteTimeout = 43200;  // seconds since sign-in
oAuth->LoginRateLimit->MaxAttempts = 5;
oAuth->OnAuthenticate = AuthAuthenticate;
oHTMX->Auth = oAuth;

// only a signed-in user reaches /orders
TsgcHTMX_Route *oRoute = oRouter->Routes->Add();
oRoute->Path = "/orders";
oRoute->RequireLogin = true;
oRoute->OnRoute = OrdersRoute;

// only a session with one of these roles reaches /admin
oRoute = oRouter->Routes->Add();
oRoute->Path = "/admin";
oRoute->RequireRoles = "admin,manager";
oRoute->OnRoute = AdminRoute;

void __fastcall TForm1::AuthAuthenticate(TObject *Sender,
  const UnicodeString aUser, const UnicodeString aPassword, bool &aAccept,
  UnicodeString &aUserID, UnicodeString &aDisplayName, TStrings *const aRoles)
{
  // LoadPasswordHash reads the value stored when the user was created
  aAccept = sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
  if (aAccept)
  {
    aUserID = aUser;
    aDisplayName = aUser;
    aRoles->Add("admin");
  }
}

// when you create a user, store this hash, never the password
String hash = sgcHTMLPasswordHash("secret");
using esegece.sgcWebSockets;

// htmx is a TsgcHTMX_Engine_Server, router is a TsgcHTMX_Router
var store = new TsgcHTMLSessionStore_Memory();
store.SweepInterval = 60;

var auth = new TsgcHTMLAuth();
auth.SessionStore = store;
auth.IdleTimeout = 1800;       // seconds without a request
auth.AbsoluteTimeout = 43200;  // seconds since sign-in
auth.LoginRateLimit.MaxAttempts = 5;
auth.OnAuthenticate += AuthAuthenticate;
htmx.Auth = auth;

// only a signed-in user reaches /orders
var route = router.Routes.Add();
route.Path = "/orders";
route.RequireLogin = true;
route.OnRoute += OrdersRoute;

// only a session with one of these roles reaches /admin
route = router.Routes.Add();
route.Path = "/admin";
route.RequireRoles = "admin,manager";
route.OnRoute += AdminRoute;

void AuthAuthenticate(object sender, string aUser, string aPassword,
    ref bool aAccept, ref string aUserID, ref string aDisplayName,
    List<string> aRoles)
{
    // LoadPasswordHash reads the value stored when the user was created
    aAccept = sgcHTMLAuthHelpers.sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
    if (aAccept)
    {
        aUserID = aUser;
        aDisplayName = aUser;
        aRoles.Add("admin");
    }
}

// when you create a user, store this hash, never the password
string hash = sgcHTMLAuthHelpers.sgcHTMLPasswordHash("secret");

关键属性与方法

您最常使用的成员。

CookieName(默认为 sgcsid)、CookiePathCookieDomain 用于配置 cookie。CookieSecureCookieHttpOnly 默认为 TrueCookieSameSitehssLax;其他取值是 hssStricthssNone,其中 hssNone 总会添加 Secure

超时

IdleTimeout(1800 秒)会结束未被使用的会话,AbsoluteTimeout(43200 秒)会在登录后经过这么长时间结束会话,无论是否有活动。0 会禁用其中任一项检查。CurrentSession 同时验证这两项,并在每个请求上更新会话的活动时间。

CSRF 令牌

CSRFProtection 默认开启。每个会话都会得到一个随机令牌:IssueCSRF(session) 返回它,ValidateCSRF(session, token) 以恒定时间比较它。引擎从 CSRFHeaderName 请求头(X-CSRF-Token)或 CSRFFieldName 字段(csrf_token)读取令牌,对缺少令牌的、会改变状态的请求返回 403。

CSRF 与 htmx

为已登录会话渲染的页面,会在 head 中带有 <meta name="csrf-token"><meta name="csrf-header">,内置的 htmx 脚本会在每个非 GET、HEAD 或 OPTIONS 的请求中,通过该请求头发送令牌。hx-posthx-delete 不需要额外的标记。

记住我

RememberMeDays(0 表示关闭,最大 3650)用于开启它,RememberCookieName 为 cookie 命名(sgcrem)。当请求没有有效会话但带有有效令牌时,OnLoadUser 会重新加载用户,签发新的会话,并轮换令牌。存储只保留验证器的哈希值。

登录速率限制

LoginRateLimit 是一个 TsgcHTMLAuthRateLimit_Options。失败的登录按客户端 IP 和用户名,在 WindowSeconds(300)内分别计数;达到 MaxAttempts(5)后,该 IP 或用户会被锁定 LockoutSeconds(900),锁定期间不会调用 OnAuthenticateMaxAttempts = 0 会禁用该限制,IsLoginLocked(ip, user) 报告是否被锁定。

密码

sgcHTMLPasswordHash(password, iterations) 返回 pbkdf2-sha256$iterations$salt$hash,默认使用随机的 16 字节盐和 210000 次迭代。sgcHTMLPasswordVerify(password, hash) 以恒定时间检查密码,对格式错误的哈希返回 False

会话存储

TsgcHTMLSessionStore_Memory 把会话保存在进程中,并通过 SweepInterval 进行后台清理;请让它的 IdleTimeoutAbsoluteTimeout 与 Auth 的相应值保持一致。TsgcHTMLSessionStore_File 为每个会话在 Folder 中写入一个 JSON 文件,因此多个进程可以共享,并且最多每隔 TouchInterval 秒(60)重写一次文件。两者都是线程安全的。

会话

TsgcHTMLSession 包含 IDUserIDDisplayNameRoles、自由的 Values(name=value)、CSRFTokenCreatedAtLastSeenRemoteIPUserAgentHash,外加 HasRole。路由通过 TsgcHTMXRequest.Session 读取它,匿名时为 nil。存储返回的是副本,因此需要调用 SessionStore.Put 来保存修改。

登录与退出

Authenticate 应用速率限制并触发 OnAuthenticateSignIn 总是创建新的会话 ID,删除旧的会话,并签发新的 CSRF 令牌,以防御会话固定攻击。SignOut 结束一个会话,SignOutEverywhere(userID) 结束某个用户的所有会话和记住我令牌。在引擎之外,请像 Admin CRUD 演示那样,自行调用 CurrentSession(cookieHeader, remoteIP, userAgent, setCookies)

路由守卫

TsgcHTMX_Route 标记 RequireLoginRequireRoles(逗号分隔,匹配其中一个即可)。匿名请求会被 302 重定向到 LoginPath?next=,如果请求来自 htmx,则得到 401 加 HX-Redirect;没有相应角色的会话得到 403。引擎还会响应向 LoginPathLogoutPath 发出的 POST,然后跳转到 AfterLoginPath

WebSocket 连接

消息使用 WebSocket 握手时所发送 cookie 对应的会话运行,前提是该握手来自同源;任何其他消息都是匿名的,被拒绝的消息不会得到响应。TsgcHTMX_Engine_Server.MessageSession(aConnection) 为自行响应 OnHTMXMessage 的宿主返回某个连接背后的会话,由调用方释放它。

继续探索

在线帮助此组件的完整 API 参考和使用指南。
会话与身份验证指南Cookie、CSRF、路由守卫、记住我和登录速率限制,逐步说明。
所有 sgcHTML 组件浏览 80 多个组件的完整功能矩阵。
下载免费试用版30 天试用版包含 60.HTML 演示项目,其中 02.AdminCRUD 通过 TsgcHTMLAuth 登录。
价格Single、Team 和 Site 授权,均含完整源代码。
超值之选:All-AccesseSeGeCe 全部产品,含高级支持,每年 €1,059 起。
查看 All-Access 价格

准备好开始了吗?

下载免费试用版,开始在 Delphi、C++ Builder 和 .NET 中构建 Web 界面。