Session和Cookie
Session和Cookie是如何来的? 首先我们想想这样一个场景,你登陆微博个人主页的时候,是不是需要登录,你登陆之后,是不是就可以跳转来到你的个人主页,这个时候还没有问题产生,因为你登陆输入了用户和密码,自然而然就可以跳转到你对应的个人页面,但是问题就来了,你要去其他页面并且保存你登陆的状态怎么验证,因为http请求是没有状态的,他不知道有没有登录,这个时候怎么办呢? 所以就需要cookie和session 来保存我们登陆的状态 Cookie:本地计算机保存一些用户操作的历史信息,并在用户再次访问该站点时浏览器通过HTTP协议将本地Cookie内容发送给服务器,从而完成验证,或继续上一步操作。
Cookie是有时间限制的,根据生命周期不同分为会话Cookie和持久Cookie
如果不设置过期时间,则表示这个Cookie生命周期从创建到浏览器关闭为止,只要关闭浏览器窗口,Cookie就消失。 如果设置了过期时间,浏览器就会把Cookie保存到硬盘上,关闭后再次打开浏览器,这些Cookie依然有效直到超过设定的过期时间。 Session:在服务器上保存用户操作的历史信息。服务器使用Session ID来标识Session,Session ID由服务器负责产生,保证随机性与唯一性,相当于一个随机密钥,避免在握手或传输中暴露用户真实密码。但该方式下,仍然需要将发送请求的客户端与Session进行对应,所以可以借助Cookie机制来获取客户端的标识(即Session ID),也可以通过GET方式将ID提交给服务器。
Go 语言对Cookie的操作
设置Cookie:
http. setCookie ( w ResponseWriter, cookie * Cookie)
cookie := http. Cookie ( )
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time. Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
SameSite SameSite
Raw string
Unparsed [ ] string
}
读取Cookie:请求.Cookie()
Go 语言如何使用Session
Session创建过程:
生成全局唯一标识符 开辟数据存储空间 将Session的全局唯一标识符发送给客户端
Cookie服务端通过设置Set-cookie头就可以将Session的标识符传送到客户端,客户端此后的每一次请求都会带上这个标识符,另外一般包含Session信息的Cookie会将失效时间设置为0,即浏览器进程有效时间。 URL重写,所谓URL重写,就是返回给用户的页面里的所有URL后面追加Session标识符,这样用户在收到响应之后,无论点击响应页面里的哪个链接或提交表单,都会自动带上Session标识符。 实现Session管理:
全局Session管理器
Manager:管理器 Provider:存储方式
SessionInit函数实现Session的初始化,操作成功则返回此新的Session变量。 SessionRead函数返回sid所代表的Session变量,如果不存在,那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量。 SessionDestroy函数用来销毁sid对应的Session变量。 SessionGC根据maxLifeTime来删除过期的数据。 保证SessionID的全局唯一性 为每个客户关联一个Session Session的存储 Session过期处理
type Manager struct {
cookieName string
lock sync. Mutex
provider Provider
maxLifeTime int64
}
func NewManger ( provideName, cookieName string , maxlifeTime int64 ) ( * Manager, error ) {
provider, ok = provides ( provideName)
if ! ok {
return nil , fmt. Errorf ( "session: 找不到对应存储方法!" )
}
return & Manager{
provider: provider,
cookieName: cookieName,
maxLifeTime: maxlifeTime,
}
}
type Provider interface {
SessionInit ( sid string ) ( Session, error )
SessionRead ( sid string ) ( Session, error )
SessionDestroy ( sid string ) error
SessionGC ( maxLifeTime int64 )
}
type Session interface {
Set ( key, value interface { } ) error
Get ( key interface { } ) interface { }
Delete ( ket interface { } ) error
SessionID ( ) string
}
func Register ( name string , provide Provider) {
if provide == nil {
panic ( "session: Register provide is nil" )
}
if _ , ok := provides[ name] ; ok {
panic ( "session: Register called twice for provide " + name)
}
provides[ name] = provide
}
var provides = make ( map [ string ] Provider)
func ( manager * Manager) sessionId ( ) string {
b := make ( [ ] byte , 32 )
if _ , err := io. ReadFull ( rand. Reader, b) ; err != nil {
return ""
}
return base64. URLEncoding. EncodeToString ( b)
}
func ( manager * Manager) SessionStart ( w http. ResponseWriter, r * http. Request) ( session Session) {
manager. lock. Lock ( )
defer manager. lock. Unlock ( )
cookie, err := r. Cookie ( manager. cookieName)
if err != nil || cookie. Value == "" {
sid := manager. sessionId ( )
session, _ = manager. provider. SessionInit ( sid)
cookie := http. Cookie{
Name: manager. cookieName,
Value: url. QueryEscape ( sid) ,
Path: "/" ,
HttpOnly: true ,
MaxAge: int ( manager. maxLifeTime) ,
Expires: time. Now ( ) . Add ( time. Duration ( manager. maxLifeTime) ) ,
}
http. SetCookie ( w, & cookie)
} else {
sid, _ := url. QueryUnescape ( cookie. Value)
session, _ = manager. provider. SessionRead ( sid)
}
return session
}
func ( manager * Manager) SessionDestroy ( w http. ResponseWriter, r * http. Request) {
cookie, err := r. Cookie ( manager. cookieName)
if err != nil || cookie. Value == "" {
return
} else {
manager. lock. Lock ( )
defer manager. lock. Unlock ( )
sid, _ := url. QueryUnescape ( cookie. Value)
manager. provider. SessionDestroy ( sid)
expiration := time. Now ( )
cookie := http. Cookie{
Name: manager. cookieName,
Path: "/" ,
HttpOnly: true ,
Expires: expiration,
MaxAge: - 1 }
http. SetCookie ( w, & cookie)
}
}
func ( manager * Manager) GC ( ) {
manager. lock. Lock ( )
defer manager. lock. Unlock ( )
manager. provider. SessionGC ( manager. maxLifeTime)
time. AfterFunc ( time. Duration ( manager. maxLifeTime) , func ( ) { manager. GC ( ) } )
}
Session 存储
var pder = & FromMemory{ list: list. New ( ) }
func init ( ) {
pder. sessions = make ( map [ string ] * list. Element, 0 )
session. Register ( "memory" , pder)
}
type SessionStore struct {
sid string
LastAccessedTime time. Time
value map [ interface { } ] interface { }
}
func ( st * SessionStore) Set ( key, value interface { } ) error {
st. value[ key] = value
pder. SessionUpdate ( st. sid)
return nil
}
func ( st * SessionStore) Get ( key interface { } ) interface { } {
pder. SessionUpdate ( st. sid)
if v, ok := st. value[ key] ; ok {
return v
} else {
return nil
}
return nil
}
func ( st * SessionStore) Delete ( key interface { } ) error {
delete ( st. value, key)
pder. SessionUpdate ( st. sid)
return nil
}
func ( st * SessionStore) SessionID ( ) string {
return st. sid
}
type FromMemory struct {
lock sync. Mutex
sessions map [ string ] * list. Element
list * list. List
}
func ( frommemory * FromMemory) SessionInit ( sid string ) ( session. Session, error ) {
frommemory. lock. Lock ( )
defer frommemory. lock. Unlock ( )
v := make ( map [ interface { } ] interface { } , 0 )
newsess := & SessionStore{ sid: sid, LastAccessedTime: time. Now ( ) , value: v}
element := frommemory. list. PushBack ( newsess)
frommemory. sessions[ sid] = element
return newsess, nil
}
func ( frommemory * FromMemory) SessionRead ( sid string ) ( session. Session, error ) {
if element, ok := frommemory. sessions[ sid] ; ok {
return element. Value. ( * SessionStore) , nil
} else {
sess, err := frommemory. SessionInit ( sid)
return sess, err
}
return nil , nil
}
func ( frommemory * FromMemory) SessionDestroy ( sid string ) error {
if element, ok := frommemory. sessions[ sid] ; ok {
delete ( frommemory. sessions, sid)
frommemory. list. Remove ( element)
return nil
}
return nil
}
func ( frommemory * FromMemory) SessionGC ( maxLifeTime int64 ) {
frommemory. lock. Lock ( )
defer frommemory. lock. Unlock ( )
for {
element := frommemory. list. Back ( )
if element == nil {
break
}
if ( element. Value. ( * SessionStore) . LastAccessedTime. Unix ( ) + maxLifeTime) <
time. Now ( ) . Unix ( ) {
frommemory. list. Remove ( element)
delete ( frommemory. sessions, element. Value. ( * SessionStore) . sid)
} else {
break
}
}
}
func ( frommemory * FromMemory) SessionUpdate ( sid string ) error {
frommemory. lock. Lock ( )
defer frommemory. lock. Unlock ( )
if element, ok := frommemory. sessions[ sid] ; ok {
element. Value. ( * SessionStore) . LastAccessedTime = time. Now ( )
frommemory. list. MoveToFront ( element)
return nil
}
return nil
}
预防Session劫持
首先先了解什么是Session劫持,Session劫持是一种广泛存在的比较严重的安全威胁,在Session技术中,客户端和服务端通过Session得标识符来维护会话,但这个标识符 很容易就能被嗅探到,从而被其他人利用,下面是例子: 虽然换了浏览器,但是我们却获得了SessionID,然后模拟了Cookie存储的过程。这个例子是在同一台计算机上做的,不过即使换用两台来做,其结果仍然一样。此时如果交替点击两个浏览器里的链接你会发现它们其实操纵的是同一个计数器。处firefox盗用了chrome和goserver之间的维持会话的钥匙,即goSessionid,这是一种类型的“会话劫持”。在goserver看来,它从http请求中得到了一个gosessionid,由于HTTP协议的无状态性,它无法得知这个gosessionid是从chrome那里“劫持”来的,它依然会去查找对应的Session,并执行相关计算。与此同时chrome也无法得知自己保持的会话已经被“劫持”。 所以我们应该如何预防呢? 第一个解决方案是SessionID的值只允许Cookie设置,而不是通过URL重置方式设置,同时设置Cookie的httponly为true,然后在每个请求加上token,防止form重复提交的功能,从而保证用户的请求唯一性
作用一:防止Cookie被XSS读取从而引起Session劫持 作用二:Cookie设置不会像URL重置方式那么容易获取SessionID 第二个解决方案是间隔生成新的SID,给Session额外设置一个创建时间的值,一旦过了一定的时间,销毁这个SessionID,重新生成新的Session,这样可以一定程度上防止Session劫持的问题