微服务体系结构是服务器应用程序体系结构中的新热点,它具有多种优势,包括易于扩展和在一个应用程序中使用多种编程语言的能力。但是我们知道,没有免费的午餐!这种灵活性会带来成本,并带来一些传统“整体式”应用程序所没有的挑战。在本文中,我们将研究一个挑战:跨服务共享会话。
传统单体软件的session管理
传统session 管理采用cookie和session 技术来实现网站的安全访问的。
Cookie技术是客户端的解决方案,Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。
这个过程如下图所示:
另一方面,会话将历史信息存储在服务器端。服务器使用会话ID来标识不同的会话,并且服务器生成的会话ID应该始终是随机且唯一的。您可以使用Cookie或URL参数来获取客户端的身份。
session 信息是存放再服务器短的。具体地,它可以存放在内存,存放再文件系统中,或者数据库中。当你采用Go 语言编程的时候,可以使用: github.com/gorilla/sessions 包
具体内容访问:http://www.gorillatoolkit.org/pkg/sessions
Go 代码
使用文件存储session 信息的例子。完整的代码参见:https://github.com/CurtisVermeeren/Gorilla-Sessions-Tutorial
微服务架构下session 管理面临的问题
当单一程序转换到微服务架构时,问题就来了。每个微服务都有web 服务器。显然不能各自搞一套session 管理是不现实的。
cookie 是否可共享?
访问多个微服务的cookie 消息是否能构共享?具体地讲,就是访问不同微服务时,是否上传同一个cookie?网上有许多说法,我看老外的描述,并且写程序验证表明:对于同一个主机上的不同端口上的web服务是可以共享cookie 的。cookie 只针对主机,而不针对端口。
session 是否可以共享?
在一个微服务器上身份验证 ,session 信息如何与其它微服务共享?可以使用共享文件或者采用数据库实现,由于我们的系统中采用了mongoDB数据库,所以我们使用mongodb 存储session信息。
在:https://github.com/kidstuff/mongostore有相应的session store 模块
Go 代码
该代码是在newfilestore 的例中修改而成的。
package main
import (
"net/http"
"fmt"
"encoding/gob"
"github.com/gorilla/sessions"
"github.com/kidstuff/mongostore"
"github.com/globalsign/mgo"
"log"
)
type User struct {
Username string
Authenticated bool
}
func loginHandle(rw http.ResponseWriter, req *http.Request) {
username:=req.FormValue("username")
password:=req.FormValue("password")
fmt.Println(username)
fmt.Println(password)
// Fetch new store.
dbsess, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer dbsess.Close()
store := mongostore.NewMongoStore(dbsess.DB("test").C("test_session"), 3600, true,
[]byte("cookie-name"))
// Get a session.
session, err := store.Get(req, "cookie-name")
if err != nil {
log.Println(err.Error())
}
// Add a value.
user := &User{
Username: username,
Authenticated: true,
}
session.Values["user"] = user
// Save.
if err = sessions.Save(req, rw); err != nil {
log.Printf("Error saving session: %v", err)
}
// fmt.Fprintln(rw, "ok")
http.Redirect(rw, req, "/secret", http.StatusFound)
}
func secretHandle(rw http.ResponseWriter, req *http.Request){
dbsess, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer dbsess.Close()
store := mongostore.NewMongoStore(dbsess.DB("test").C("test_session"), 3600, true,
[]byte("cookie-name"))
session, err := store.Get(req, "cookie-name")
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
user := getUser(session)
fmt.Println(user.Username)
if auth := user.Authenticated; !auth {
session.AddFlash("You don't have access!")
err = session.Save(req, rw)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(rw, req, "/forbidden.html", http.StatusFound)
return
}
http.Redirect(rw, req, "/secretcontent.html", http.StatusFound)
}
func logoutHandle(rw http.ResponseWriter, req *http.Request){
dbsess, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer dbsess.Close()
store := mongostore.NewMongoStore(dbsess.DB("test").C("test_session"), 3600, true,
[]byte("cookie-name"))
session, err := store.Get(req, "cookie-name")
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
session.Values["user"] = User{}
session.Options.MaxAge = -1
err = session.Save(req, rw)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(rw, req, "/", http.StatusFound)
}
func getUser(s *sessions.Session) User {
val := s.Values["user"]
var user = User{}
user, ok := val.(User)
if !ok {
return User{Authenticated: false}
}
return user
}
func main() {
fmt.Printf("sessions test\n")
gob.Register(User{})
fs := http.FileServer(http.Dir("./www"))
http.Handle("/", fs)
http.HandleFunc("/login",loginHandle)
http.HandleFunc("/logout",logoutHandle)
http.HandleFunc("/secret",secretHandle)
http.ListenAndServe(":8900", nil)
}
实验时,运行两个相同的程序,分别是8900和8901 端口,一个程序login 另一个访问 http:localhost:8901/secret 能访问到
secret content
meeting start at AM 6:00
的页面。
有问题,请留言。