21. maj 2007 - 22:12Der er
33 kommentarer og 1 løsning
Loginsystem fra ASP til ASP.NET
Hej eksperter
Jeg har en drøm! I min drøm har jeg lavet min eksisterende side lavet med ASP.Classic om til ASP.NET. Det langt overskyggende problem i denne sammenhæng er at bruger-strukturen på denne side er meget kompleks, og skal beskrives ved 3 parametre i modsætning til den ene parameter der er standard i ASP.NET Logins. Desuden ville jeg ønske at jeg kunne fortsætte med den eksisterende MySQL-database. Mit problem er altså kort sagt at lave et login-system der opfylder nedenstående krav.
Jeg har forsøgt at efterligne den gamle funktionalitet ved at lave et par klasser der henholdsvis indeholder en brugers data ("Person") og en der kan bruges til at returnere en collection af personer ("objPerson").
I min ønskedrøm er sidens login-system lavet sådan at man bruger et standard <asp:Login>-tag, som når man logger ind måske kalder metoden "ValidateUser" i "objPerson" og sætter User-objektet til den "Person" der hører til brugeren der er logget ind.
Desuden skal man kunne styre adgangen til de beskyttede sider udfra alle de tre parametre der er nødvendige for at beskrive bruger-strukturen. Én parameter styrer meget overordnede ting som om brugeren er Webmaster, Powerbruger osv. (Det er denne der hedder Person.Status) De sidste to parametre skal bare sendes til metoden User.Er(parameter1, parameter2) der jo vil være tilgængelig hvis User er lavet til et "Person"-objekt.
Kan dette laves og hvordan skal jeg gribe det an? Er der noget der er uforståeligt?
Kunne man måske lave den sidste del med beskyttelsen af siderne vha. et HttpModule der når man henter noget, tjekker med en database om det er beskyttet og så redirect'er hvis brugeren ikke jar adgang... Det var bare en tanke jeg lige fik... Kan det lade sig gøre?
Men hvis du har en løsning til noget som helst af det ovenstående vil jeg meget gerne høre fra dig!
Her en som jeg har brugt. Oprindeligt c# men jeg har lige konverteret den til vb: Imports System.Web.Security Imports System.Configuration.Provider Imports System.Collections.Specialized Imports System Imports System.Data Imports System.Configuration Imports System.Diagnostics Imports System.Web Imports System.Globalization Imports System.Security.Cryptography Imports System.Text Imports System.Web.Configuration
Public NotInheritable Class TIMemberShipProvider Inherits MembershipProvider Private newPasswordLength As Integer = 2 Private eventSource As String = "TIMemberShipProvider" Private eventLog As String = "Application" Private exceptionMessage As String = "An exception occurred. Please check the Event Log." Private connectionString As String Private machineKey As MachineKeySection Private pWriteExceptionsToEventLog As Boolean
Public Property WriteExceptionsToEventLog() As Boolean Get Return pWriteExceptionsToEventLog End Get Set pWriteExceptionsToEventLog = value End Set End Property
Public Overloads Overrides Sub Initialize(ByVal name As String, ByVal config As NameValueCollection) If config Is Nothing Then Throw New ArgumentNullException("config") End If If name Is Nothing OrElse name.Length = 0 Then name = "TIMemberShipProvider" End If If String.IsNullOrEmpty(config("description")) Then config.Remove("description") config.Add("description", "Membershipprovider til TI's egenkontrol") End If MyBase.Initialize(name, config) pApplicationName = GetConfigValue(config("applicationName"), System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath) pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config("maxInvalidPasswordAttempts"), "5")) pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config("passwordAttemptWindow"), "10")) pMinRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config("minRequiredNonAlphanumericCharacters"), "1")) pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config("minRequiredPasswordLength"), "7")) pPasswordStrengthRegularExpression = Convert.ToString(GetConfigValue(config("passwordStrengthRegularExpression"), "")) pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config("enablePasswordReset"), "true")) pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config("enablePasswordRetrieval"), "true")) pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config("requiresQuestionAndAnswer"), "false")) pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config("requiresUniqueEmail"), "true")) pWriteExceptionsToEventLog = Convert.ToBoolean(GetConfigValue(config("writeExceptionsToEventLog"), "true")) Dim temp_format As String = config("passwordFormat") If temp_format Is Nothing Then temp_format = "Clear" End If Select temp_format Case "Hashed" pPasswordFormat = MembershipPasswordFormat.Hashed ' break Case "Encrypted" pPasswordFormat = MembershipPasswordFormat.Encrypted ' break Case "Clear" pPasswordFormat = MembershipPasswordFormat.Clear ' break Case Else Throw New ProviderException("Password format not supported.") End Select Dim cfg As Configuration = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath) machineKey = CType(cfg.GetSection("system.web/machineKey"), MachineKeySection) End Sub
Private Function GetConfigValue(ByVal configValue As String, ByVal defaultValue As String) As String If String.IsNullOrEmpty(configValue) Then Return defaultValue End If Return configValue End Function Private pApplicationName As String Private pEnablePasswordReset As Boolean Private pEnablePasswordRetrieval As Boolean Private pRequiresQuestionAndAnswer As Boolean Private pRequiresUniqueEmail As Boolean Private pMaxInvalidPasswordAttempts As Integer Private pPasswordAttemptWindow As Integer Private pPasswordFormat As MembershipPasswordFormat
Public Overloads Overrides Property ApplicationName() As String Get Return pApplicationName End Get Set pApplicationName = value End Set End Property
Public Overloads Overrides ReadOnly Property EnablePasswordReset() As Boolean Get Return pEnablePasswordReset End Get End Property
Public Overloads Overrides ReadOnly Property EnablePasswordRetrieval() As Boolean Get Return pEnablePasswordRetrieval End Get End Property
Public Overloads Overrides ReadOnly Property RequiresQuestionAndAnswer() As Boolean Get Return pRequiresQuestionAndAnswer End Get End Property
Public Overloads Overrides ReadOnly Property RequiresUniqueEmail() As Boolean Get Return pRequiresUniqueEmail End Get End Property
Public Overloads Overrides ReadOnly Property MaxInvalidPasswordAttempts() As Integer Get Return pMaxInvalidPasswordAttempts End Get End Property
Public Overloads Overrides ReadOnly Property PasswordAttemptWindow() As Integer Get Return pPasswordAttemptWindow End Get End Property
Public Overloads Overrides ReadOnly Property PasswordFormat() As MembershipPasswordFormat Get Return pPasswordFormat End Get End Property Private pMinRequiredNonAlphanumericCharacters As Integer
Public Overloads Overrides ReadOnly Property MinRequiredNonAlphanumericCharacters() As Integer Get Return pMinRequiredNonAlphanumericCharacters End Get End Property Private pMinRequiredPasswordLength As Integer
Public Overloads Overrides ReadOnly Property MinRequiredPasswordLength() As Integer Get Return pMinRequiredPasswordLength End Get End Property Private pPasswordStrengthRegularExpression As String
Public Overloads Overrides ReadOnly Property PasswordStrengthRegularExpression() As String Get Return pPasswordStrengthRegularExpression End Get End Property
Public Overloads Overrides Function ChangePassword(ByVal username As String, ByVal oldPwd As String, ByVal newPwd As String) As Boolean Dim args As ValidatePasswordEventArgs = New ValidatePasswordEventArgs(username, newPwd, True) OnValidatingPassword(args) If args.Cancel Then If Not (args.FailureInformation Is Nothing) Then Throw args.FailureInformation Else Throw New MembershipPasswordException("Change password canceled due to new password validation failure.") End If End If If Bruger.SkiftPassword(username, newPwd) Then Return True Else Return False End If End Function
Public Overloads Overrides Function ChangePasswordQuestionAndAnswer(ByVal username As String, ByVal password As String, ByVal newPasswordQuestion As String, ByVal newPasswordAnswer As String) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function CreateUser(ByVal username As String, ByVal password As String, ByVal email As String, ByVal passwordQuestion As String, ByVal passwordAnswer As String, ByVal isApproved As Boolean, ByVal providerUserKey As Object, ByRef status As MembershipCreateStatus) As MembershipUser Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function DeleteUser(ByVal username As String, ByVal deleteAllRelatedData As Boolean) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetAllUsers(ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As MembershipUserCollection Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetNumberOfUsersOnline() As Integer Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetUser(ByVal username As String, ByVal userIsOnline As Boolean) As MembershipUser ' Using Dim ds As DataSet = Bruger.FindBrugerInfoMedBrugerNavn(username) Try Try Dim dr As DataRow = ds.Tables(0).Rows(0) Dim u As MembershipUser = New MembershipUser(Me.Name, dr("BrugerNavn").ToString, dr("BrugerID"), dr("Email").ToString, "", "", True, False, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now) Return u Catch e As Exception WriteToEventLog(e, "GetUser") Return Nothing End Try Finally CType(ds, IDisposable).Dispose() End Try End Function
Public Overloads Overrides Function GetUser(ByVal providerUserKey As Object, ByVal userIsOnline As Boolean) As MembershipUser Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function UnlockUser(ByVal username As String) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetUserNameByEmail(ByVal email As String) As String Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function ResetPassword(ByVal username As String, ByVal answer As String) As String Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Sub UpdateUser(ByVal user As MembershipUser) Throw New Exception("The method or operation is not implemented.") End Sub
Public Overloads Overrides Function ValidateUser(ByVal username As String, ByVal password As String) As Boolean username = HttpUtility.HtmlEncode(username) password = HttpUtility.HtmlEncode(password) If Not (Bruger.ValiderLogind(username, password, HttpContext.Current.Request.UserHostAddress) = -1) Then Return True Else Return False End If End Function
Public Overloads Overrides Function FindUsersByName(ByVal usernameToMatch As String, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As MembershipUserCollection Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function FindUsersByEmail(ByVal emailToMatch As String, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As MembershipUserCollection Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetPassword(ByVal username As String, ByVal answer As String) As String Throw New Exception("The method or operation is not implemented.") End Function
Private Sub WriteToEventLog(ByVal e As Exception, ByVal action As String) Dim log As EventLog = New EventLog log.Source = eventSource log.Log = eventLog Dim message As String = "An exception occurred communicating with the data source." & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & "" message += "Action: " + action + "" & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & "" message += "Exception: " + e.ToString log.WriteEntry(message) End Sub End Class
her er en role provider: Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls
Public Class MyCustomRoleProvider Inherits SqlRoleProvider
Public Overloads Overrides Sub Initialize(ByVal name As String, ByVal config As System.Collections.Specialized.NameValueCollection) End Sub
Public Overloads Overrides Sub CreateRole(ByVal roleName As String) Throw New Exception("The method or operation is not implemented.") End Sub
Public Overloads Overrides Function DeleteRole(ByVal roleName As String, ByVal throwOnPopulatedRole As Boolean) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Sub AddUsersToRoles(ByVal usernames As String(), ByVal roleNames As String()) Throw New Exception("The method or operation is not implemented.") End Sub
Public Overloads Overrides Property ApplicationName() As String Get Throw New Exception("The method or operation is not implemented.") End Get Set Throw New Exception("The method or operation is not implemented.") End Set End Property
Public Overloads Overrides ReadOnly Property Description() As String Get Throw New Exception("The method or operation is not implemented.") End Get End Property
Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function FindUsersInRole(ByVal roleName As String, ByVal usernameToMatch As String) As String() Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetAllRoles() As String() Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetHashCode() As Integer Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function GetRolesForUser(ByVal username As String) As String() Dim userdata As String = "" Dim accesslevel As Integer = Bruger.HentAdgangsNiveauID(HttpUtility.HtmlEncode(username)) Select accesslevel Case 1 userdata = "Mekaniker" ' break Case 2 userdata = "Mekaniker,Bruger" ' break Case 3 userdata = "Mekaniker,Bruger,Egenkontrollør" ' break Case 4 userdata = "Mekaniker,Bruger,Egenkontrollør,Ti_Kontrollør" ' break Case 5 userdata = "Mekaniker,Bruger,Egenkontrollør,Ti_Kontrollør,Admin" ' break End Select Dim roles As String() = userdata.Split(","C) Return roles End Function
Public Overloads Overrides Function GetUsersInRole(ByVal roleName As String) As String() Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function IsUserInRole(ByVal username As String, ByVal roleName As String) As Boolean Return Bruger.IsUserInRole(username, roleName) End Function
Public Overloads Overrides ReadOnly Property Name() As String Get Return "MyCustomRoleProvider" End Get End Property
Public Overloads Overrides Sub RemoveUsersFromRoles(ByVal usernames As String(), ByVal roleNames As String()) Throw New Exception("The method or operation is not implemented.") End Sub
Public Overloads Overrides Function RoleExists(ByVal roleName As String) As Boolean Throw New Exception("The method or operation is not implemented.") End Function
Public Overloads Overrides Function ToString() As String Throw New Exception("The method or operation is not implemented.") End Function End Class
I begge providers er det meste af koden standard undtagen i få metoder hvor jeg laver kald til en klasse kaldet bruger. Her er nogle shared metoder som laver validering af brugernavn og password. f.eks: username = HttpUtility.HtmlEncode(username) password = HttpUtility.HtmlEncode(password) If Not (Bruger.ValiderLogind(username, password, HttpContext.Current.Request.UserHostAddress) = -1) Then Return True Else Return False End If
Min login side er næsten standard og jeg bruger en login kontrol.
okay... Så din "Bruger"-klasse er altså en der minder om min "Person" eller hvad? Hvilken en af de klasser skal svare til hvilken af mine? Eller er jeg bare lidt forvirret...
Jeg tror aldrig jeg har fattet meningen med en Role Provider... Mit problem er jo netop at jeg ikke kan adskille brugerne i nogle simple adskildte "roller".
Hvis vi nu snakker helt overordnet... Hvad gør de klasser hver især...? Jeg går ud fra det er MemberShipProvideren der står for at kalde ValidateUser når man logger ind fra et <asp:Login>-tag - Det er jo sådan set fuldstændig perfekt!
Men det er mere det her med hvordan jeg så beskytter siderne jeg ikke helt forstår...
Min bruger er lignende din person klasse. I min roleprovider til deler jeg brugeren et array af roller. Det er ikke sådan at hver person har en rolle men er medlem af en række forskellige roller. Hvordan styrer du adgangen til de enkelte sider ?
Membership provideren er meget basal og står stortset kun for at skifte password og validerer password på en bruger og hente lidt brugerinfo. Som du kan se er der mange metoder som ikke er implementeret. Roleprovideren til deler brugeren en række roller og indeholder en metode til at spørge om en bruger er i en bestemt rolle.
Okay. Mit problem her bliver at, som jeg skrev i toppen, at jeg ikke kan nøjes med en "rolle"-parameter for at beskrive brugerstrukturen - Jeg behøver hele 3! Den måde jeg gjorde i gamle dage (ASP.Classic) var at i toppen af siden at lave forskellige tjek på Person.Status og Person.Er(). F.eks: <% If Not (Person.Status = 1 And Person.Er("Formand","Bestyrelsen")) Then Response.Redirect("/Login_IngenAdgang.asp") End If %>
Dette kunne være en måde jeg tjekkede på, på den gamle ASP.Classic side... Og jeg har også nu behov for at kunne tjekke på alle disse tre parametre...
Person.Status er nok det der kommer tættest på et decideret rolle-system... Den indeholder om personen er Webmaster, Powerbruger osv... Altså en overordnet status. Dertil kommer så de to andre parametre, som kan kombineres på alle mulige måder, så det er en kombination af mange "roller" som hver person har. F.eks. kan en person være leder i én afdeling, medlem i en anden og desuden være formand for bestyrelsen... (Tænkt eksempel) Giver det mening?
hmm det lyder mildest talt en anelse bøvlet. Hvis det var optil mig ville jeg ændre lidt i det så jeg havde BestyrelsesFormand BestyrelsesMedlem AfdelingLeder osv. Hvis man er BestyrelsesFormand har man også automatisk rolen BestyrelsesMedlem osv. Jeg tror ikke du kan bruge det indbyggede rolle system på den måde du gjorde i asp. Kan hvis du laver If Not (Person.Status = 1 And Person.Er("Formand","Bestyrelsen")) Then Response.Redirect("/Login_IngenAdgang.asp") End If i page_load af hver side. Men det er ikke en måde jeg ville gøre det på.
Mht log ind kan du sagtens bruge den membershipprovider jeg har vist dig( i modificeret form.).
Problemet er bare at sådan kan man desværre ikke bryde det op... Det er en meget "flad" organisation, dvs. der er mange uafhængige grene i strukturen og mulighed for at være med i flere på en gang. Men jeg vil lige rode lidt mere med den side af det og så vende tilbage...
Hvordan beskytter du egentlig dine sider? Er det noget med at skrive noget i web.config?
ja typisk laver jeg noget i web.config på denne måde: <location path="Kontakt.aspx"> <system.web> <authorization> <allow roles="Egenkontrollør,User,Administrator"/> <deny users="*"/> </authorization> </system.web> </location>
//metode som bruges til at validerer at det indtastede brugenavn og password //giver adgang til siden. public static int ValiderLogind(string brugernavn, string password, string ip) { int rowsAffected; //Definer parameterne til den storedprocedure SqlParameter[] parameters = { new SqlParameter("@BrugerNavn",SqlDbType.VarChar, 50), new SqlParameter("@Password", SqlDbType.VarChar, 50), new SqlParameter("@IP", SqlDbType .VarChar, 25), new SqlParameter("@BrugerID", SqlDbType.Int, 4) }; //gem de indkommende værdier i parameterne parameters[0].Value = brugernavn; parameters[1].Value = password; parameters[2].Value = ip; parameters[3].Direction = ParameterDirection.Output; try { //forsøg at afvikle den stored procedure og returner enten brugerid eller -1 DbHandler dbha = new DbHandler(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString); dbha.RunProcedure("SP_Bruger_ValiderLogind", parameters, out rowsAffected); return (int)parameters[3].Value; } catch (Exception e) { //hvis der er en fejl udskrives denne Fejl.OpretNyFejl(int.Parse(HttpContext.Current.Profile.GetPropertyValue("BrugerInfo.BrugerID").ToString()), e); //og -1 returneres return -1; }
Hej igen! Nu har jeg tænkt lidt over om det er muligt at kunne opdele mine brugere i nogle roller der ville passe på et sådant system... Og det tror jeg måske godt vil kunne lade sig gøre langt hen af vejen... Det eneste problem er i et par få enkelt-tilfælde hvor en adgangen på en side skal begrænses til f.eks. rollen "Webmaster" plus et par specielle brugere der har nogle specielle egenskaber, men dog skal høre til den samme rolle som alle andre brugere. Lyder det kringlet, kan jeg godt forklare det lidt nærmere...
Men kan dette evt. løses ved at lave noget ala ASP.Classic-løsningen på de få sider hvor det er aktuelt. Altså noget lignende (skitse): <script> sub Page_Load() if User.Status = 4 And Jeg.Er("Formand") ' Tillad at jeg ser siden alligevel... end if end sub </script>
og så kunne man beskytte siden med <allow roles="Webmaster" />
Eller skal man vende den om... Altså med : <allow roles="Webmaster,Brugere" /> <script> sub Page_load() if Not User.Status = "Webmaster" And Not User.Er("Formand") then ' Jeg har ikke adgang... Send mig væk fra siden! end if end sub </script>
Du skal gøre noget med: <allow roles="*" /> sub Page_load() if Not User.Status = "Webmaster" And Not User.Er("Formand") then ' Jeg har ikke adgang... Send mig væk fra siden! end if end sub
Men den kan give problemer hvis du bruger en asp:menu. Men eller er det en fin løsning.
Ja, for det kan ikke lade sig gøre at lave sådan noget <allow roles="Webmaster" subrole="Formand" /> vel?
Den næste hurtle: Hvordan kan jeg gøre så når en bruger logger ind bliver hans "Person" gemt f.eks. i User-objektet så det kan bruges f.eks. det vi lige snakkede om... Og den skal vel fjernes igen når brugeren logger ud...
Ja, det var selvfølgelig en løsning... Men nu har de jo allerede lavet Page.User-objektet i ASP.NET der jo indeholder lidt af det samme, og så synes jeg det kunne være fedt hvis man ligesom kunne "udvide" User så den kommer til at indeholde alle de egenskaber fra "Person"-klassen... Er det ikke en god måde at gøre det på, eller hvad?
Jo det er det. Du kan også kigge på profile objektet hvor du kan lave dine egne properties og sætte dem. Jeg har ikke selv prøvet at udvide user objektet.
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.