В 2000 году, если вы разрабатывали корпоративное веб-приложение для серверов на Windows, вы использовали Active Server Pages. Стек Microsoft: Windows NT 4 или Windows 2000 Server, IIS 5, SQL Server 7 или 2000, скриптование на VBScript.
PHP работал на Unix и требовал Apache. Perl требовал знания Unix. ASP работал на Windows, управлялся стандартными инструментами Microsoft, интегрировался с Active Directory и использовал VBScript - язык, который IT-отделы уже знали по макросам Office. Для компаний, вложившихся в инфраструктуру Microsoft, ASP был очевидным выбором.
Система авторизации с управлением сессиями - первая реальная задача веб-приложения. Полная реализация 2000 года.
Объект Session: встроенное управление состоянием
Объект Session в ASP - это серверный словарь на каждого пользователя, идентифицируемый куки (ASPSESSIONID), который IIS устанавливал автоматически. Никакого ручного управления куки - IIS управлял сессионным куки, а код просто читал и писал в Session как в словарь.
<%
' login.asp - Classic ASP, авторизация с SQL Server
' VBScript, IIS 5, SQL Server 2000, 2000 год
Option Explicit
Dim strError
strError = ""
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim strUsername, strPassword
strUsername = Trim(Request.Form("username"))
strPassword = Trim(Request.Form("password"))
If strUsername = "" Or strPassword = "" Then
strError = "Логин и пароль обязательны."
Else
Dim bAuthenticated
bAuthenticated = AuthenticateUser(strUsername, strPassword)
If bAuthenticated Then
' Сохраняем идентификатор пользователя в сессии
Session("IsLoggedIn") = True
Session("Username") = strUsername
Session("LoginTime") = Now()
Response.Redirect "dashboard.asp"
Response.End
Else
strError = "Неверный логин или пароль."
End If
End If
End If
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Вход</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body bgcolor="#FFFFFF">
<h2>Вход в систему</h2>
<% If strError <> "" Then %>
<p><font color="red"><b><%= Server.HTMLEncode(strError) %></b></font></p>
<% End If %>
<form method="POST" action="login.asp">
<table border="0" cellpadding="4">
<tr>
<td>Логин:</td>
<td><input type="text" name="username" size="25"></td>
</tr>
<tr>
<td>Пароль:</td>
<td><input type="password" name="password" size="25"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Войти"></td>
</tr>
</table>
</form>
</body>
</html>
Аутентификация через ADODB
<%
' Функция аутентификации - часть login.asp или отдельный include-файл
Function AuthenticateUser(strUsername, strPassword)
AuthenticateUser = False
Dim oConn, oRS, oCmd
Set oConn = Server.CreateObject("ADODB.Connection")
' DSN-less строка подключения к SQL Server 2000
oConn.Open "Provider=SQLOLEDB;" & _
"Data Source=SQLSERVER01;" & _
"Initial Catalog=MyAppDB;" & _
"User ID=webapp_user;" & _
"Password=webapp_pass;"
' ВНИМАНИЕ: многие разработчики в 2000 году конкатенировали строки напрямую.
' Это - SQL-инъекция. Правильный подход уже тогда: параметризованные запросы.
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "SELECT UserID, PasswordHash, IsActive " & _
"FROM Users WHERE Username = ?"
oCmd.CommandType = 1 ' adCmdText
' Параметр - защита от SQL-инъекций
oCmd.Parameters.Append oCmd.CreateParameter("@username", 200, 1, 50, strUsername)
' 200 = adVarChar, 1 = adParamInput, 50 = макс. длина
Set oRS = oCmd.Execute()
If Not oRS.EOF Then
Dim storedHash
storedHash = oRS("PasswordHash")
' Сравнение хеша - MD5 был стандартом в 2000 году
' (bcrypt появился в веб-разработке только к 2007-2010 годам)
If MD5Hash(strPassword) = storedHash Then
If CBool(oRS("IsActive")) Then
AuthenticateUser = True
Call LogLoginAttempt(CStr(oRS("UserID")), True)
End If
Else
Call LogLoginAttempt("0", False)
End If
End If
oRS.Close
oConn.Close
Set oRS = Nothing
Set oCmd = Nothing
Set oConn = Nothing
End Function
%>
Защита страниц через проверку сессии
Каждая защищённая страница включала в начало файл с проверкой - директива #include в ASP позволяла переиспользовать этот код:
<%
' auth_check.asp - включать в начало каждой защищённой страницы
' Использование: <!-- #include file="auth_check.asp" -->
If Not CBool(Session("IsLoggedIn")) Then
Response.Redirect "login.asp?return=" & Server.URLEncode(Request.ServerVariables("URL"))
Response.End
End If
' Проверка таймаута сессии - 30 минут неактивности
Dim dtLoginTime
dtLoginTime = Session("LoginTime")
If IsDate(dtLoginTime) Then
If DateDiff("n", dtLoginTime, Now()) > 30 Then
Session.Abandon
Response.Redirect "login.asp?msg=timeout"
Response.End
End If
End If
Session("LoginTime") = Now()
%>
Защищённая страница:
<%
' dashboard.asp
Option Explicit
%>
<!-- #include file="auth_check.asp" -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Личный кабинет</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>
<h1>Добро пожаловать, <%= Server.HTMLEncode(Session("Username")) %>!</h1>
<p>Вы вошли в систему. <a href="logout.asp">Выйти</a></p>
</body>
</html>
Выход:
<%
' logout.asp
Session.Abandon ' Очищает все переменные сессии, инвалидирует куки
Response.Redirect "login.asp"
Response.End
%>
Схема таблицы SQL Server
-- SQL Server 2000, запускать в Query Analyzer
CREATE TABLE Users (
UserID INT IDENTITY(1,1) PRIMARY KEY,
Username VARCHAR(50) NOT NULL UNIQUE,
PasswordHash VARCHAR(32) NOT NULL, -- MD5 в hex, 32 символа
Email VARCHAR(100) NOT NULL,
IsActive BIT NOT NULL DEFAULT 1,
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
LastLogin DATETIME NULL
)
GO
CREATE UNIQUE INDEX IX_Users_Username ON Users (Username)
GO
CREATE TABLE LoginLog (
LogID INT IDENTITY(1,1) PRIMARY KEY,
UserID INT NOT NULL,
LoginDate DATETIME NOT NULL DEFAULT GETDATE(),
IPAddress VARCHAR(15) NOT NULL,
Success BIT NOT NULL
)
GO
Вставка пользователя с хешем пароля:
<%
' register.asp
' MD5 в Classic ASP: обычно подключали внешний include-файл md5.asp
' <!-- #include file="md5.asp" -->
Dim strHashedPassword
strHashedPassword = MD5(Request.Form("password"))
Dim oConn, oCmd
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open strConnectionString
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "INSERT INTO Users (Username, PasswordHash, Email) VALUES (?, ?, ?)"
oCmd.Parameters.Append oCmd.CreateParameter("@u", 200, 1, 50, strUsername)
oCmd.Parameters.Append oCmd.CreateParameter("@ph", 200, 1, 32, strHashedPassword)
oCmd.Parameters.Append oCmd.CreateParameter("@email", 200, 1, 100, strEmail)
oCmd.Execute
oConn.Close
Set oCmd = Nothing
Set oConn = Nothing
%>
Объект Application и Global.asa
Пока Session был уникальным для каждого пользователя, Application был общим для всех:
<%
' Используется в Global.asa, Application_OnStart
Application.Lock
Application("ConnectionString") = "Provider=SQLOLEDB;Data Source=..."
Application("SiteVersion") = "2.1.4"
Application("StartTime") = Now()
Application.Unlock
%>
Global.asa - файл событий приложения, аналог bootstrap в современных фреймворках:
' Global.asa - размещается в корне веб-приложения
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub Application_OnStart
Application("TotalVisits") = 0
End Sub
Sub Session_OnStart
Application.Lock
Application("TotalVisits") = Application("TotalVisits") + 1
Application.Unlock
Session.Timeout = 30 ' минуты
End Sub
Sub Session_OnEnd
' Очистить ресурсы сессии
End Sub
</SCRIPT>
Переход на ASP.NET
В 2000-2001 году Microsoft разрабатывал ASP.NET, вышедший в январе 2002 года. Архитектурный сдвиг был значительным: от интерпретируемого VBScript к компилируемому C# или VB.NET, от ручного управления сессиями и формами к модели Web Forms с серверными контролами, от ADODB.Recordset к ADO.NET dataset.
Classic ASP разработчики, понимавшие модель объектов Session/Application/Request/Response, переходили на ASP.NET органично - те же абстракции, реализованные лучше. Classic ASP оставался в продакшене долгие годы после выхода ASP.NET; многие приложения работали на нём вплоть до эры Windows Server 2008, когда хостеры начали его депрекировать.
Паттерны - сессионная аутентификация, параметризованные запросы к БД, серверный рендеринг с include-файлами - это паттерны современной веб-разработки. Технология сменилась; архитектура осталась прежней.