让登录变得更安全

我们上一节介绍的用户登录,无法避免同名用户重复登录,而且直接通过Cookie存储用户密码 在安全方面也是有欠缺的。

在安全方面,我的建议是:

1、Cookie名称不要用UserName和PassWord等指示性很强的名称。
2、内容加密存储,关于字符的加密和机密,参考:加密和解密函数
3、不要设置Cookie的有效时间,这样Cookie会随浏览器的关闭而消失。

如果你觉得以上措施还不足以让你在安全方面放心,而且需要禁止重复登录,可以考虑下面的方法。

设计任务

1、不能在Cookie存储密码,即使时加密后的密码,也不能出现在Cookie中。
2、同名用户在其他电脑登录后(实际开发时,可以用不同的浏览器来模拟不同电脑),前一次的登录自动失效。
3、如果用户30分钟内没有操作,或关闭浏览器,登录失效。

设计思路

1、服务端新建一个临时表,包括三列,分别是:UserName,UserID和ActiveTime。

2、服务端在验证用户身份后,在临时表中新增一行,生成一个16位的随机字符串存储在UserID列,用户名和当前时间则分别存储在UserName和ActiveTime列。

3、完成上述操作后,再将UserName和UserID保存在Cookie中,其中UserName存储在Cookie中的是加密后的字符串。

4、为方便理解,本节的示例代码直接使用username和userid作为Cookie名称,实际开发的时候,请采用没有任何指示性的名称,注意Cookie名是区分大小写的,这里用的是小写。

5、用户在登录成功,继续访问其他页面时,Cookie中会包括username和userid,服务端根据username和userid从临时表中查找对应的行,此时会有三种可能:

6、如果用户在其他位置登录成功,服务端根据加密后的UserName在临时表中查找对应的行,如果找到,说明是重复登录,删除此行,使得前一次的登录无效 ;然后重复2、3操作,在临时表和Cookie中存储加密后的UserName和随机生成的UserID,并在ActiveTime列记录当前时间。

7、显然临时表的记录会越来越多,我们可以定期清除ActiveTime列和当前时间相比超过30分钟的行,也可以在表在超过约定行数后执行一次清理动作,本示例选择的是前者,每30分钟清理一次。

HttpRequest事件代码

以下是根据上述设计思路整理出来的HttpRequest事件代码,由于Cookie中的用户名是加密存储的,而且Cookie不会出现密码,出现的是随机生成UserID, 且每次登录UserID都不同,所以安全性会大大提高。

看起来代码有点长,这是因为完成的任务比较多,如果你分开任务看,每一个任务的代码都很精简,也很好理解:

第1到14行代码用于生成临时表和清除过期登录信息;
第21到47行代码用于根据用户输入的用户名和密码进行身份验证,并在在临时表和Cookie中保存登录信息;
第49到第58行代码用于从Cookie中提取登录信息进行身份验证,并更新最后一次活动时间;
第63到第74行代码用于生成登录页面;
第76到第78行代码用于退出登录;
第80到第94行代码用于生成首页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

Static UserTable As DataTable '定义一个变量,用于存储用户随机身份ID,以及最后一次活动时间.
Static
ClearTime As Date
If
UserTable Is Nothing Then '创建用于记录登录信息的临时表
    ClearTime = Date.Now()
   
Dim dtb As New DataTableBuilder("UserInfos")
    dtb.AddDef("UserName", Gettype(String), 16)
    dtb.AddDef("UserID",Gettype(String),16)
    dtb.AddDef("ActiveTime",Gettype(Date))
    UserTable = dtb.Build(True)

End
If
If
(Date.Now - ClearTime).TotalMinutes >= 30 Then '清除超过30分钟没有操作的登录信息
    UserTable.DeleteFor("ActiveTime < #" & Date.Now.AddMinutes(-30) & "#")
    ClearTime = Date.Now()
End
If
Dim wb As New weui
'
身份验证
Dim
UserName As String
Dim
Password As String
Dim
UserID As String
If
e.Path = "logon.htm" '验证用户名和密码
    If e.PostValues.ContainsKey("username") AndAlso e.PostValues.ContainsKey("password"Then
        Dim Verified As Boolean
'
用于标记用户是否通过了身份验证
        UserName = e.PostValues("username")
        Password = e.PostValues("password")
        If UserName =
"
张三" AndAlso Password = "888" Then  '实际使用的时候,请改为从数据库读取用户名和密码进行比较
            Verified  = True
        ElseIf Username =
"
李四" AndAlso Password="999" Then
            Verified  = True
        End If
        If Verified Then
            UserID = Rand.NextString(16)
'
生成随机用户ID
            UserName = EncryptText(UserName,"123","123")
'
将用户名加密.
            Dim dr As DataRow = UserTable.Find("UserName = '" & UserName & "'")
            If  dr IsNot Nothing Then
'
如果是重复登录,删除以前的登录信息
                dr.Delete()
            End If
            dr = UserTable.AddNew()
            dr("UserName") = UserName
            dr("UserID") = UserId
            dr("ActiveTime") = Date.Now
'
记录登录时间
            wb.AppendCookie("username",UserName)
'
将用户名和UserID写入cookie
            wb.AppendCookie("userid",UserID)
            wb.InsertHTML("<meta http-equiv='Refresh' content='0; url=/default.htm'>")
'
直接跳转到首页
            e.WriteString(wb.Build)
'
生成网页
            Return
'
必须的
        End If
    End
If

Else
'其它页面从Cookie提取登录信息进行验证
    UserName = e.Cookies("username"
'
cookie中获取用户名
    UserID = e.Cookies("userid"
'
cookie中获取 随机ID
    Dim dr As DataRow = UserTable.Find("UserName = '" & UserName & "'")
    If dr IsNot Nothing AndAlso dr("UserID") = UserID Then 
'
如果通过验证,更新活动时候,继续访问其它页面.
        dr("ActiveTime") = Date.Now
'
更新活动时间
    Else
'
如果验证失败
        wb.InsertHTML("<meta http-equiv='Refresh' content='0; url=/logon.htm'>")
'
那么直接跳转到登录页面
        e.WriteString(wb.Build)
'
生成网页
        Return
'
必须的
    End
If

End
If
'
开始生成网页
Select
Case e.path
   
Case "logon.htm" '登录页面
        wb.AddPageTitle("","pageheader","销售系统","由湛江辉迅基于Foxtable开发")
        If e.PostValues.ContainsKey("username") AndAlso e.PostValues.ContainsKey("password"Then '判断是否是验证失败后的重新登录
            wb.AddTopTips("","toptip1","用户名或密码错误!").msec = 2000 '如果用户通过登录按钮访问,则给用户一个2秒的提示.
       
End If
        wb.AddForm("","form1","logon.htm")
        With wb.AddInputGroup("form1","ipg1")
            .AddInput("username",
"
户名","text")
            .AddInput("password",
"
密码","password")
        End With
        With wb.AddButtonGroup("form1","btg1",True)
            .Add("btn1",
"
登录", "submit")
        End With
    Case "exit.htm"
'
退出登录
        wb.DeleteCookie("username")
'
清除cookie中原来的用户名和UserID
        wb.DeleteCookie("UserID")
        wb.InsertHTML("<meta http-equiv='Refresh' content='0; url=/logon.htm'>")
'
然后直接跳转到登录页面
    Case "", "default.htm"
'
首页
        wb.AddPageTitle("","pageheader","销售系统","由湛江辉迅基于Foxtable开发")
        With wb.AddGrid("","g1")
            .Add("c1",
"
增加订单", "./images/button.png").Attribute = "onclick='javascript:alert(""你单击了我!"")'"
            .Add("c2",
"
客户管理", "./images/cell.png", "http://www.foxtable.com")
            .Add("c3",
"
销售统计", "./images/toast.png", "http://www.foxtable.com")
            .Add("c4","Dialog", "./images/dialog.png", "http://www.foxtable.com")
            .Add("c5","Progress", "./images/progress.png", "http://www.foxtable.com")
            .Add("c6","Msg", "./images/msg.png", "http://www.foxtable.com")
            .Add("c7","Article", "./images/article.png", "http://www.foxtable.com")
            .Add("c8","ActionSheet", "./images/actionSheet.png", "http://www.foxtable.com")
            .Add("c9","Icons", "./images/icons.png", "http://www.foxtable.com")
            .Add("c10","Panel", "./images/panel.png", "http://www.foxtable.com")
            .Add("c11","Tab", "./images/tab.png", "http://www.foxtable.com")
            .Add("c12",
"
退出", "./images/exit.png", "exit.htm") '退出登录
        End
With

End
Select
e
.WriteString(wb.Build) '生成网页

提示:用户表以及最近一次清理过期信息的时间,用静态变量保存,关于静态变量,参考:使用静态变量


本页地址:http://www.foxtable.com/mobilehelp/topics/0154.htm