(MVC) MVC (2015 год)

Формування безопасного токена для зміни паролю.

Формування безопасного токена для зміни паролю На цієї сторінці я продовжу потрохи описувати свій новий фреймворк, який я зробив собі за декілька останніх років для роботи під ASP.NET MVC - замість ось цого фреймворку під ASP.NET, який я використовував з 2005-го року і опублікував у 2009-му році ось тут AspNet_UserManager - компонент сайта для управления пользователями. Я вже описав дещо з цього нового фреймворку, наприклад Поштові розсилки через Gmail.

Попередник функцій, описаних на цій сторінці, був описан у мене п'ять років тому ось тут - Избавляемся от базы стандартных пользователей ASP.NET на MS SQL - пример ASP.NET сайта на MySQL - на цей сторінці описана незламна та достатньо надійна технология, але з роками і вона була модифікована у кращу сторону.

Функція надійної зміни паролю є найважливіша, мені відомо що навіть великі банківські системи мають уразливість на атаку на зміну паролей, наприклад гаманець KIWI - це не гаманець зовсім, а дірка від гаманця, бо робили цю функцію дурні. Тому я тут і опишу, як це треба робити корректно.


Загальний принцип формування безопасного токену (тобто при використовуванні на сайті це просто особливим чином сформований URL) полягає у таких моментах:

Таким чином, модифікація мого попереднього фреймворку полягає в тому, що

  • одноразовий тікет видається на невеличкий термін життя
  • безпосередньо у URL міститься хеш, який дозволяє перевірити коректність усього SecurityToken'у, тобто URL - без звернення до бази, тобто "на льоту", що покращує стійкість сайту до атак на грубий підбір паролей
  • у конфигу сайту зберігається параметр, який можно у будь-який час змінити і цим зробити недійснім разом усі видані сайтом URL на зміну паролю.

Тепер подивимося безпосередньо на код. По-перше, є табличка юзерів Users:


   1:  CREATE TABLE [dbo].[Users](
   2:      [i] [int] IDENTITY(1,1) NOT NULL,
   3:      [ID] [uniqueidentifier] NOT NULL,
   4:      [FirstName] [nvarchar](50) NOT NULL,
   5:      [LastName] [nvarchar](50) NOT NULL,
   6:      [Email] [nvarchar](50) NOT NULL,
   7:      [Password] [nvarchar](15) NOT NULL,
   8:      [IsAdmin] [int] NOT NULL,
   9:      [CrDate] [datetime] NOT NULL,
  10:      [LastLoginDate] [datetime] NOT NULL,
  11:      [DailyMail] [int] NULL,
  12:      [WeeklyMail] [int] NULL,
  13:      [RestorePassID] [uniqueidentifier] NULL,
  14:   
  15:  ....
  16:   
  17:   CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED 
  18:  (
  19:      [i] ASC
  20:  )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
  21:  ) ON [PRIMARY]
  22:   
  23:  GO

У якій нам найважливо побачити поле [ID] (це ID юзера) та [RestorePassID] (це одноразовий тікет на зміну пароля).

Ця табличка відмапірована за допомогою мапера Linq-To-SQL на файл OCMR.dbml:


   1:  <?xml version="1.0" encoding="utf-8"?><Database Name="Ocmr" Class="OCMRDataContext" xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">
   2:    <Connection Mode="WebSettings" ConnectionString="Data Source=AAA.BBB.CCC.DDD;Initial Catalog=Ocmr;User ID=Ocmr;Max Pool Size=10000" SettingsObjectName="System.Configuration.ConfigurationManager.ConnectionStrings" SettingsPropertyName="OCMR_ConnectionStrings" Provider="System.Data.SqlClient" />
   3:    <Table Name="dbo.Users" Member="Users">
   4:      <Type Name="User">
   5:        <Column Name="i" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
   6:        <Column Name="ID" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
   7:        <Column Name="FirstName" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
   8:        <Column Name="LastName" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
   9:        <Column Name="Email" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
  10:        <Column Name="Password" Type="System.String" DbType="NVarChar(15) NOT NULL" CanBeNull="false" />
  11:        <Column Name="IsAdmin" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
  12:        <Column Name="CrDate" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
  13:        <Column Name="LastLoginDate" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
  14:        <Column Name="DailyMail" Type="System.Int32" DbType="Int" CanBeNull="true" />
  15:        <Column Name="WeeklyMail" Type="System.Int32" DbType="Int" CanBeNull="true" />
  16:        <Column Name="RestorePassID" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
  17:   
  18:  ....
  19:   
  20:      </Type>
  21:    </Table>
  22:  ....

Також ми маємо у проєкті конфіг, що містить по перше конект до бази (у данному випадку це OCMR_ConnectionStrings, а по-друге той самий параметр SecretConstant:


   1:  <?xml version="1.0"?>
   2:   
   3:  <configuration>
   4:    <appSettings>
   5:  ....
   6:      <add key="SecretConstant" value= "Reset Password "/>
   7:  ....
   8:      <add key="CacheImageReiseMode1" value="1"/>
   9:      <add key="CacheImageReiseMode2" value="2"/>
  10:      <add key="webpages:Version" value="1.0.0.0"/>
  11:      <add key="ClientValidationEnabled" value="true"/>
  12:      <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  13:    </appSettings>
  14:    <connectionStrings>
  15:      <remove name="LocalSqlServer"/>
  16:      <add name="OCMR_ConnectionStrings" connectionString="server=AAA.BBB.CCC.DDD;Initial Catalog=Ocmr;User ID=Ocmr;Password=XXXXXXXXXXXXX;Max Pool Size=10000;" providerName="System.Data.SqlClient"/>
  17:  ....
  18:       </connectionStrings>
  19:    <system.web>
  20:      <compilation debug="true" targetFramework="4.0">
  21:   
  22:      </compilation>
  23:   
  24:      <authentication mode="Forms">
  25:        <forms loginUrl="~/Account/LogOn" timeout="2880" />
  26:      </authentication>
  27:   
  28:      <pages>
  29:        <namespaces>
  30:          <add namespace="System.Web.Helpers" />
  31:          <add namespace="System.Web.Mvc" />
  32:          <add namespace="System.Web.Mvc.Ajax" />
  33:          <add namespace="System.Web.Mvc.Html" />
  34:          <add namespace="System.Web.Routing" />
  35:          <add namespace="System.Web.WebPages"/>
  36:        </namespaces>
  37:      </pages>
  38:    </system.web>
  39:   
  40:    <system.webServer>
  41:      <validation validateIntegratedModeConfiguration="false"/>
  42:      <modules runAllManagedModulesForAllRequests="true"/>
  43:    </system.webServer>
  44:   
  45:    <runtime>
  46:      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  47:        <dependentAssembly>
  48:          <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
  49:          <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
  50:        </dependentAssembly>
  51:      </assemblyBinding>
  52:    </runtime>
  53:  </configuration>

Проєкт містить контроллер Login, який має вісімь сторінок. Ці сторінки гранично примітивні, але для подальшого порозуміння коду, імена їх полей потрібні.

Формування безопасного токена для зміни паролю
Index.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:      <div style="margin-left:20px">
   3:   
   4:  <h2>Login & Register</h2>
   5:  <% Using Html.BeginForm("Index", "Login", Nothing, FormMethod.Post)%>
   6:   
   7:      <br /><br />
   8:      Email<br />
   9:      <input id="name" name="name" type="text" style="width:150px" /><br /><br />
  10:      Password<br />
  11:      <input id="pass" name="pass" type="password" style="width:150px" /><br /><br />
  12:      Remember Me<br />
  13:      <%: Html.CheckBox("remember")%> <br /><br />
  14:   
  15:      <input id="Submit1" type="submit" value="Login" style="width:150px" />
  16:   
  17:   <% End Using %>
  18:   
  19:   <br /><br />
  20:   <%: Html.ActionLink( "Register new user", "Register")%><br />
  21:   <%: Html.ActionLink( "Lost Password", "LostPassword")%>
  22:   
  23:  </div>
  24:  </asp:Content>

Logout.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:  <h2>Logout</h2>
   4:  <br /><br />
   5:  <h3>Exit from site</h3>
   6:  <br /><br />
   7:  <% Using Html.BeginForm("Logout", "Cab")%>
   8:  <input id="Submit1" type="submit" value="Logout" style="width:150px" />
   9:  <%     End Using%>
  10:   
  11:  </asp:Content>

LostPassword.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:      <h2>Lost Password</h2>
   4:   
   5:  <h3>To restore password, please enter you Email</h3>
   6:   
   7:  <% Using Html.BeginForm("LostPassword", "Login")%>
   8:   
   9:      <br /><br />
  10:      you Email<br />
  11:   
  12:      <input id="email" name="email" type="text" style="width:150px"  maxlength="20" placeholder="Enter you Email" required  /><br /><br />
  13:      <br /><br />
  14:      <input id="Submit1" type="submit" value="OK" style="width:150px" />
  15:   
  16:   <% End Using %>
  17:   
  18:  </asp:Content>

Register.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:  <h2>Register new user</h2>
   4:   
   5:  <% Using Html.BeginForm("Register",  "Login") %>
   6:   
   7:      <br /><br />
   8:      First Name<br />
   9:      <input id="first" name="first" type="text" style="width:150px"  maxlength="50" placeholder="Enter your First Name" required /><br /><br />
  10:      Last Name<br />
  11:      <input id="last" name="last" type="text" style="width:150px"  maxlength="50"  placeholder="Enter your Last Name" required  /><br /><br />
  12:      Email<br />
  13:      <input id="email" name="email" type="email" style="width:150px"  maxlength="50" placeholder="Enter your email" required /><br /><br />
  14:      Password<br />
  15:      <input id="pass" name="pass" type="password" style="width:150px"  maxlength="20" placeholder="Enter your password" required  /><br /><br />
  16:   
  17:      <input id="Submit1" type="submit" value="Register" style="width:150px" />
  18:   
  19:   <% End Using %>
  20:   <br />
  21:   <font color="red"><%: ViewData("Err1")%></font>
  22:   
  23:  </asp:Content>

ResetPassword.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:  <h2><%: Model.FirstName%> <%: Model.LastName%>, please enter you new password:</h2>
   4:  <br /><br />
   5:   
   6:  <% Using Html.BeginForm("ResetPassword", "Login")%>
   7:   
   8:      <br /><br />
   9:      New Password<br />
  10:      <%: Html.Hidden("ID", RouteData.Values("ID").ToString)%>
  11:      <%: Html.Hidden("UID", Request.QueryString("UID"))%>
  12:      <%: Html.Hidden("Token", request.querystring("Token"))%>
  13:   
  14:      <input id="pass" name="pass" type="password" style="width:150px"  maxlength="20" placeholder="Enter new password" required  /><br /><br />
  15:      <br /><br />
  16:      <input id="Submit1" type="submit" value="Set" style="width:150px" />
  17:   
  18:   <% End Using %>
  19:   
  20:  </asp:Content>

ResetPasswordMSG.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:  <h2><%: Model.FirstName%> <%: Model.LastName%>, please enter you new password:</h2>
   4:  <br /><br />
   5:   
   6:  <% Using Html.BeginForm("ResetPassword", "Login")%>
   7:   
   8:      <br /><br />
   9:      New Password<br />
  10:      <%: Html.Hidden("ID", RouteData.Values("ID").ToString)%>
  11:      <%: Html.Hidden("UID", Request.QueryString("UID"))%>
  12:      <%: Html.Hidden("Token", request.querystring("Token"))%>
  13:   
  14:      <input id="pass" name="pass" type="password" style="width:150px"  maxlength="20" placeholder="Enter new password" required  /><br /><br />
  15:      <br /><br />
  16:      <input id="Submit1" type="submit" value="Set" style="width:150px" />
  17:   
  18:   <% End Using %>
  19:   
  20:  </asp:Content>

RestorePass.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:      <h2>Restore Password</h2>
   4:   
   5:  <h3>Restore my password through <%:Model.Email%>&nbsp; ?</h3>
   6:  <br /><br />
   7:  <% Using Html.BeginForm("RestorePass", "Login")%>
   8:  <%: Html.Hidden("Email", Model.ID)%>
   9:  <input id="Submit1" type="submit" value="Send" style="width:150px" />
  10:  <%     End Using%>
  11:   
  12:  </asp:Content>

RestorePassMSG.aspx
   1:  <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:   
   3:      <h3>Mail to <%:Model.Email%> sended</h3>
   4:  <br /><br />
   5:   
   6:  <% Using Html.BeginForm("Index", "Home", Nothing, FormMethod.Get)%>
   7:  <br /><br />
   8:  <input id="Submit1" type="submit" value="OK" style="width:150px" />
   9:  <%     End Using%>
  10:   
  11:  </asp:Content>

Код контроллера нескладний, але містить усе, про було сказано вище. Тут є звернення до функції розсилки мила, яку я вже описував колись раніше - Поштові розсилки через Gmail. А також є редірект на сторінки кабінетів юзерів, залежно від прав юзерів (41-47).

Одноразовий токен утворюється у стрічках 197, 233, 249


   1:  Namespace OCMR
   2:      Public Class LoginController
   3:          Inherits System.Web.Mvc.Controller
   4:   
   5:   
   6:          Function Index() As ActionResult
   7:              Return View()
   8:          End Function
   9:   
  10:          <HttpPost()> _
  11:          Function Index(PRM As FormCollection) As ActionResult
  12:              Dim Name As String = PRM("name")
  13:              Dim Pass As String = PRM("pass")
  14:              Dim Remember As String = PRM("remember")
  15:              If Name Is Nothing Or Pass Is Nothing Or Remember Is Nothing Then
  16:                  Return View("Index")
  17:                  Exit Function
  18:              End If
  19:              If Name.Trim = "" Or Pass.Trim = "" Or Remember.Trim = "" Then
  20:                  Return View("Index")
  21:                  Exit Function
  22:              End If
  23:              Dim OCMR_DB As New OCMRDataContext
  24:              Dim UserID = (From X In OCMR_DB.Users Select X Where X.Email.Trim = Name.Trim And X.Password.Trim = Pass.Trim).ToList
  25:              If UserID.Count = 0 Then
  26:                  Return View("Index")
  27:                  Exit Function
  28:              End If
  29:              'аутентификация
  30:              If Remember.Contains("true") Then
  31:                  Dim ticket As New FormsAuthenticationTicket(1, UserID(0).Email, Now, Now.AddYears(1), True, "", FormsAuthentication.FormsCookiePath)
  32:                  Dim EncryptTicket As String = FormsAuthentication.Encrypt(ticket)
  33:                  Dim AUCook As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, EncryptTicket)
  34:                  AUCook.Domain = System.Configuration.ConfigurationManager.AppSettings("LoginDomain")
  35:                  AUCook.Expires = Now.AddYears(1)
  36:                  Response.Cookies.Add(AUCook)
  37:              Else
  38:                  FormsAuthentication.SetAuthCookie(UserID(0).Email, True)
  39:              End If
  40:              'редирект в кабинет
  41:              If UserID(0).IsAdmin = 0 Then
  42:                  Return RedirectToActionPermanent("Index", "Cab")
  43:              ElseIf UserID(0).IsAdmin = 1 Then
  44:                  Return RedirectToActionPermanent("Index", "Admin")
  45:              ElseIf UserID(0).IsAdmin = 2 Then
  46:                  Return RedirectToActionPermanent("Index", "SuperAdmin")
  47:              End If
  48:          End Function
  49:   
  50:          Function Register() As ActionResult
  51:              Return View()
  52:          End Function
  53:   
  54:          'в єту форму просто так не зайти - чтобы не было атаки на сброс паролей, сначала надо записать в базу RestorePassID - это входной ID этой формы
  55:          Function RestorePass(ID As String) As ActionResult
  56:              Dim db1 As New OCMRDataContext
  57:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
  58:              If AllUser.Count = 0 Then
  59:                  Return RedirectToActionPermanent("Index", "Home")
  60:              Else
  61:                  Return View("RestorePass", New With {.ID = ID, .Email = AllUser(0).Email})
  62:              End If
  63:          End Function
  64:   
  65:          'ID - это RestorePassID в базе
  66:          <HttpPost()> _
  67:          Function RestorePass(PRM As FormCollection, ID As String) As ActionResult
  68:              Dim Email As String = PRM("Email").ToString
  69:              Dim db1 As New OCMRDataContext
  70:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
  71:              If AllUser.Count = 0 Then
  72:                  Return RedirectToActionPermanent("Index", "Home")
  73:              Else
  74:   
  75:                  Dim HostingURL As String = "http://" & System.Configuration.ConfigurationManager.AppSettings("HostingURL")
  76:                  Dim NameURL As String = System.Configuration.ConfigurationManager.AppSettings("NameURL")
  77:                  Dim MD5String As String = MD5.Calculate(SecretConstant & AllUser(0).RestorePassID.ToString)
  78:                  Dim SecureURL As String = HostingURL & "/Login/ResetPassword/" & ID & "?UID=" & AllUser(0).ID.ToString & "&Token=" & MD5String
  79:                  Dim MailBody As String = _
  80:                      "<html><body>Hello, " & AllUser(0).FirstName & " " & AllUser(0).LastName & "<br><br>" & _
  81:                  "If you want to reset password in site " & "<a href=""" & HostingURL & """>" & NameURL & "</a>, <br><br>" & _
  82:                  "please go to " & "<a href=""" & SecureURL & """>" & SecureURL & "</a>, <br><br>" & _
  83:                  "Best regards, Administration of " & NameURL & " <br></body></html>"
  84:                  GMail.SendMail(AllUser(0).Email, "Restore password", MailBody)
  85:                  Return RedirectToAction("RestorePassMSG", New With {.id = ID})
  86:              End If
  87:          End Function
  88:   
  89:          Dim SecretConstant As String = System.Configuration.ConfigurationManager.AppSettings("SecretConstant")
  90:   
  91:          Function RestorePassMSG(ID As String) As ActionResult
  92:              Dim db1 As New OCMRDataContext
  93:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
  94:              If AllUser.Count = 0 Then
  95:                  Return RedirectToActionPermanent("Index", "Home")
  96:              Else
  97:                  Return View("RestorePassMSG", New With {.ID = ID, .Email = AllUser(0).Email})
  98:              End If
  99:          End Function
 100:   
 101:          <HttpPost()> _
 102:          Function RestorePassMSG(PRM As FormCollection) As ActionResult
 103:              Return RedirectToActionPermanent("Index", "Home")
 104:          End Function
 105:   
 106:          'Ссылка из мыла http://localhost:52796/Login/ResetPassword/a018a5b3-9f2d-43d7-bd84-9ebe48923eb4?UID=84193ab6-150e-4578-bc98-d77ea5375a31&Token=58E6D259DF79C68692D3128812C5E4A5, 
 107:          Function ResetPassword(ID As String) As ActionResult
 108:              Dim UID As String = Request.QueryString("UID")
 109:              Dim Token As String = Request.QueryString("Token")
 110:              If ID Is Nothing Or UID Is Nothing Or Token Is Nothing Then
 111:                  Return RedirectToActionPermanent("Index", "Home")
 112:                  Exit Function
 113:              End If
 114:              If ID.Trim = "" Or UID.Trim = "" Or Token.Trim = "" Then
 115:                  Return RedirectToActionPermanent("Index", "Home")
 116:                  Exit Function
 117:              End If
 118:              If Len(Token) <> 32 Then
 119:                  Return RedirectToActionPermanent("Index", "Home")
 120:                  Exit Function
 121:              End If
 122:              Dim UID_ID As Guid
 123:              Dim ID_ID As Guid
 124:              Try
 125:                  UID_ID = Guid.Parse(UID)
 126:              Catch ex As Exception
 127:                  Return RedirectToActionPermanent("Index", "Home")
 128:                  Exit Function
 129:              End Try
 130:              Try
 131:                  ID_ID = Guid.Parse(ID)
 132:              Catch ex As Exception
 133:                  Return RedirectToActionPermanent("Index", "Home")
 134:                  Exit Function
 135:              End Try
 136:              Dim MD5String As String = MD5.Calculate(SecretConstant & ID)
 137:              If MD5String <> Token Then
 138:                  Return RedirectToActionPermanent("Index", "Home")
 139:                  Exit Function
 140:              End If
 141:              'только теперь пошли в базу
 142:              Dim db1 As New OCMRDataContext
 143:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID And X.ID.ToString = UID).ToList
 144:              If AllUser.Count = 0 Then
 145:                  Return RedirectToActionPermanent("Index", "Home")
 146:                  Exit Function
 147:              End If
 148:              Return View(New With {.ID = ID, .FirstName = AllUser(0).FirstName, .LastName = AllUser(0).LastName, .UID = UID, .Token = Token})
 149:          End Function
 150:   
 151:          <HttpPost()> _
 152:          Function ResetPassword(PRM As FormCollection) As ActionResult
 153:              Dim ID As String = PRM("ID")
 154:              Dim UID As String = PRM("UID")
 155:              Dim Token As String = PRM("Token")
 156:              Dim NewPass As String = PRM("Pass")
 157:              If ID Is Nothing Or UID Is Nothing Or Token Is Nothing Or NewPass Is Nothing Then
 158:                  Return RedirectToActionPermanent("Index", "Home")
 159:                  Exit Function
 160:              End If
 161:              If ID.Trim = "" Or UID.Trim = "" Or Token.Trim = "" Or NewPass.Trim = "" Then
 162:                  Return RedirectToActionPermanent("Index", "Home")
 163:                  Exit Function
 164:              End If
 165:              If Len(Token) <> 32 Then
 166:                  Return RedirectToActionPermanent("Index", "Home")
 167:                  Exit Function
 168:              End If
 169:              Dim UID_ID As Guid
 170:              Dim ID_ID As Guid
 171:              Try
 172:                  UID_ID = Guid.Parse(UID)
 173:              Catch ex As Exception
 174:                  Return RedirectToActionPermanent("Index", "Home")
 175:                  Exit Function
 176:              End Try
 177:              Try
 178:                  ID_ID = Guid.Parse(ID)
 179:              Catch ex As Exception
 180:                  Return RedirectToActionPermanent("Index", "Home")
 181:                  Exit Function
 182:              End Try
 183:              Dim MD5String As String = MD5.Calculate(SecretConstant & ID)
 184:              If MD5String <> Token Then
 185:                  Return RedirectToActionPermanent("Index", "Home")
 186:                  Exit Function
 187:              End If
 188:              'только теперь пошли в базу
 189:              Dim db1 As New OCMRDataContext
 190:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID And X.ID.ToString = UID).ToList
 191:              If AllUser.Count = 0 Then
 192:                  Return RedirectToActionPermanent("Index", "Home")
 193:                  Exit Function
 194:              End If
 195:              'изменили пароль и сбросили токен для запрета повторного входа без нового мыла
 196:              AllUser(0).Password = NewPass
 197:              AllUser(0).RestorePassID = Guid.NewGuid
 198:              db1.SubmitChanges()
 199:              Return RedirectToActionPermanent("ResetPasswordMSG", New With {.id = AllUser(0).RestorePassID})
 200:          End Function
 201:   
 202:          Function ResetPasswordMSG(ID As String)
 203:              Dim db1 As New OCMRDataContext
 204:              Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
 205:              If AllUser.Count = 0 Then
 206:                  Return RedirectToActionPermanent("Index", "Home")
 207:                  Exit Function
 208:              End If
 209:              Return View(New With {.FirstName = AllUser(0).FirstName, .LastName = AllUser(0).LastName})
 210:          End Function
 211:   
 212:          Function LostPassword() As ActionResult
 213:              Return View()
 214:          End Function
 215:   
 216:          <HttpPost()> _
 217:          Function LostPassword(PRM As FormCollection) As ActionResult
 218:              Dim Email As String = PRM("email")
 219:              If Email Is Nothing Then
 220:                  Return RedirectToActionPermanent("Index", "Home")
 221:                  Exit Function
 222:              End If
 223:              If Email.Trim = "" Then
 224:                  Return RedirectToActionPermanent("Index", "Home")
 225:                  Exit Function
 226:              End If
 227:              Dim db1 As New OCMRDataContext
 228:              Dim AllUser = (From X In db1.Users Select X Where X.Email = Email).ToList
 229:              If AllUser.Count = 0 Then
 230:                  Return RedirectToActionPermanent("Index", "Home")
 231:                  Exit Function
 232:              End If
 233:              AllUser(0).RestorePassID = Guid.NewGuid
 234:              db1.SubmitChanges()
 235:              Return RedirectToAction("RestorePass", New With {.ID = AllUser(0).RestorePassID})
 236:          End Function
 237:   
 238:          <HttpPost()> _
 239:          Function Register(PRM As FormCollection) As ActionResult
 240:              Try
 241:                  Dim Email As String = PRM("email")
 242:                  Dim First As String = PRM("first")
 243:                  Dim Last As String = PRM("last")
 244:                  Dim Pass As String = PRM("pass")
 245:                  '
 246:                  Dim db1 As New OCMRDataContext
 247:                  Dim AllUser = (From X In db1.Users Select X Where X.Email.Trim = Email.Trim).ToList
 248:                  If AllUser.Count > 0 Then
 249:                      AllUser(0).RestorePassID = Guid.NewGuid
 250:                      db1.SubmitChanges()
 251:                      Return RedirectToAction("RestorePass", New With {.ID = AllUser(0).RestorePassID})
 252:                  End If
 253:                  '
 254:                  Dim NewUser = New User With { _
 255:                  .ID = Guid.NewGuid, _
 256:                  .FirstName = First, _
 257:                  .LastName = Last, _
 258:                  .Email = Email, _
 259:                  .Password = Pass, _
 260:                  .CrDate = Now, _
 261:                  .LastLoginDate = Now, _
 262:                  .DailyMail = 0, _
 263:                  .WeeklyMail = 0, _
 264:                  .IsAdmin = 0}
 265:                  db1.Users.InsertOnSubmit(NewUser)
 266:                  db1.SubmitChanges()
 267:   
 268:                  ' FormsAuthentication.SetAuthCookie(Prm("txLogin"), True) - или просто
 269:                  Dim ticket As New FormsAuthenticationTicket(1, Email, Now, Now.AddYears(1), True, "", FormsAuthentication.FormsCookiePath)
 270:                  Dim EncryptTicket As String = FormsAuthentication.Encrypt(ticket)
 271:                  Dim AUCook As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, EncryptTicket)
 272:                  AUCook.Domain = System.Configuration.ConfigurationManager.AppSettings("LoginDomain")
 273:                  Response.Cookies.Add(AUCook)
 274:                  Return RedirectToAction("Index", "Cab")
 275:              Catch ex As Exception
 276:                  ViewData("Err1") = ex.Message
 277:                  Return View()
 278:              End Try
 279:          End Function
 280:   
 281:          Function Logout() As ActionResult
 282:              If Not User.Identity.IsAuthenticated Then Return RedirectToActionPermanent("Index", "Home")
 283:              Return View()
 284:          End Function
 285:   
 286:          <HttpPost()> _
 287:          Function Logout(Prm As FormCollection) As ActionResult
 288:              FormsAuthentication.SignOut()
 289:              Return RedirectToActionPermanent("Index", "Home")
 290:          End Function
 291:   
 292:      End Class
 293:   
 294:  End Namespace

Функція MD5, яку я добавив до своєї старої логики, щоб зв'язати одноразовий тікет з ID юзера та параметром SecretConstant у конфигі, виглядає ось так:


   1:  Public Class MD5
   2:   
   3:      Public Shared Function Calculate(Str1 As String) As String
   4:          'step 1, calculate MD5 hash from input
   5:          Dim md5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create()
   6:          Dim inputBytes As Byte() = System.Text.Encoding.Unicode.GetBytes(Str1)
   7:          Dim hash As Byte() = md5.ComputeHash(inputBytes)
   8:          'step 2, convert byte array to hex string
   9:          Dim sb As StringBuilder = New StringBuilder
  10:          Dim I As Integer
  11:          While I < hash.Length
  12:              sb.Append(hash(I).ToString("X2"))
  13:              I += 1
  14:          End While
  15:          Return sb.ToString()
  16:      End Function
  17:   
  18:  End Class

У стрічці 77 контроллеру ця функція первинно утворює параметр Token для URL, а потім (у стрічках 137 і 184 контроллеру) вона порівнюється з тим, що приходить в URL від юзера.

Зверніть увагу, що у цьому варіанті мого кода формується токен, який не мае терміну дійсності. Для формування токена, який з часом застаріває треба у цю функцію ввести поточний час.



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/ChangePasswordToken/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>