我使用Gorilla会话(通过negroni-sessions)将我的用户会话存储在Cookie中。我还使用github.com/unrolled/render
进行HTML模板渲染:
main.go:
package main
import (
...
"github.com/codegangsta/negroni"
"github.com/goincremental/negroni-sessions"
"github.com/goincremental/negroni-sessions/cookiestore"
"github.com/julienschmidt/httprouter"
"github.com/unrolled/render"
...
)
func init() {
...
ren = render.New(render.Options{
Directory: "templates",
Layout: "layout",
Extensions: []string{".html"},
Funcs: []template.FuncMap{TemplateHelpers},
IsDevelopment: false,
})
...
}
func main() {
...
router := httprouter.New()
router.GET("/", HomeHandler)
// Add session store
store := cookiestore.New([]byte("my password"))
store.Options(sessions.Options{
//MaxAge: 1200,
Domain: "",
Path: "/",
})
n := negroni.New(
negroni.NewRecovery(),
sessions.Sessions("cssession", store),
negroni.NewStatic(http.Dir("../static")),
)
n.UseHandler(router)
n.Run(":9000")
}
正如您在上面所看到的,我使用 layout.html 主HTML模板,该模板在任何页面呈现时都会包含在内,例如我的主页:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
ren.HTML(w, http.StatusOK, "home", model)
}
在我的 layout.html 主HTML模板中,我想呈现一个管理菜单,但前提是当前用户是管理员:
layout.html :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ template "title" . }}</title>
...
</head>
<body>
...
<!--Main Menu-->
<nav class="menu">
<ul class="catalog">
<li class="has-submenu">
{{ RenderMenuCategories }}
</li>
<li><a href="javascript:void(0)">Blog</a></li>
<li><a href="javascript:void(0)">Company</a></li>
{{ RenderAdminMenu }}
</ul>
</nav>
...
我的问题是上面的模板助手函数RenderAdminMenu()
无法访问HTTP Request对象,因此无法访问用户会话对象以确定用户是否为admin。
我可以通过Home页面处理程序将User对象传递给模板上下文,并使用if
语句RenderAdminMenu()函数,就像这样
{{ if .User.IsAdmin }}
{{ RenderAdminMenu }}
{{ end }}
...但由于我使用的是主模板,因此必须从网站上的每个网页进行操作。有更有效的方法吗?
我想也许可能有办法从Context
(或layout.html)中访问包含RenderAdminMenu()
详细信息的某种全局Request
对象(就像你可以在ASP.NET)
答案 0 :(得分:3)
要将这些结合在一起,您需要做一些事情。我不会展示一个完整的例子,因为它既相当冗长又可能与您的代码(您尚未发布)相匹配。它将包含基本构建块:如果您遇到困难,请回过头来直接提问和代码片段,您将获得更直接的答案:)
在[login]处理程序中编写一些中间件或逻辑,用户在登录时将用户数据保存在会话中.userID,email和admin boolean值就足够了。 e.g。
// In your login handler, once you've retrieved the user &
// matched their password hash (scrypt, of course!) from the DB.
session.Values["user"] = &youruserobject
err := session.Save(r, w)
if err != nil {
// Throw a HTTP 500
}
注意:如果您想存储自己的类型,请记住,gorilla/sessions docs需要gob.Register(&youruserobject{})
。
写一个助手,当你把它拉出会话时键入断言你的类型,例如
var ErrInvalidUser= errors.New("invalid user stored in session")
func GetUser(session *sessions.Session) (*User, error) {
// You can make the map key a constant to avoid typos/errors
user, ok := session.Values["user"].(*User)
if !ok || user == nil {
return nil, ErrInvalidUser
}
return user, nil
}
// Use it like this in a handler that serves user content
session, err := store.Get("yoursessionname", r)
if err != nil {
// Throw a HTTP 500
}
user, err := GetUser(session)
if err != nil {
// Re-direct back to the login page or
// show a HTTP 403 Forbidden, etc.
}
写一些东西来检查返回的用户是否是管理员:
func IsAdmin(user *User) bool {
if user.Admin == true && user.ID != "" && user.Email != "" {
return true
}
return false
}
将其传递给模板:
err := template.Execute(w, "sometemplate.html", map[string]interface{}{
"admin": IsAdmin(user),
"someotherdata": someStructWithData,
}
// In your template...
{{ if .admin }}{{ template "admin_menu" }}{{ end }}
另外,请确保您为会话Cookie设置了身份验证密钥(阅读大猩猩文档),最好是加密密钥,并且您通过HTTPS为您的网站设置了安全:true标志。< / p>
请记住,上述方法也得到了简化:如果您在数据库中将用户取消标记为admin,则只要会话持续,应用程序就会继续以管理员身份检测它们。默认情况下,这可能是7天,因此如果您处于风险管理流失是一个真正的问题的环境中,可能需要付出真正的短会话或点击{{1}内的数据库功能只是为了安全。如果它是一个个人博客而且只是你,而不是那么多。
已添加:如果您想将用户对象直接传递给模板,您也可以这样做。请注意,在处理程序/中间件中执行它比在模板逻辑中执行它更高效。您还可以获得更多错误处理的灵活性,以及之前“纾困”的选项 - 即如果会话中没有任何内容,您可以启动HTTP 500错误,而不是渲染半个模板或者必须在您的文档中放入大量逻辑用于处理IsAdmin
数据的模板。
您仍然需要在会话中存储nil
对象(或等效对象),并从User
检索它,然后才能将其传递给模板。
session.Values
答案 1 :(得分:1)
似乎你无法从模板或模板助手函数访问Request
上下文(所以我接受了上面的答案)。我的解决方案是创建一个Page
结构,我将其作为每个模板的上下文传递。它包含Content
作为通用接口,User
对象以及其他有用的参数:
//Page holds the model to be rendered for every HTTP handler.
type Page struct {
MetaTitle string
User User
HeaderStyles string
HeaderScripts string
FooterScripts string
Content interface{}
}
func (pg *Page) Init(r *http.Request) {
if pg.MetaTitle == "" {
pg.MetaTitle = "This is the default <title> content for the page!"
}
user, _ := GetUserFromSession(r)
pg.User = *user
if user.IsAdmin() {
pg.HeaderStyles += `<link href="/css/libs/summernote/summernote.css" rel="stylesheet">`
pg.FooterScripts += `<script src="/js/libs/summernote/summernote.min.js"></script>`
}
}
Init方法允许我设置默认值并更轻松地使用Page结构,只需指定页面的Content
,如果这就是我需要的全部内容:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
pg := &Page{Content: model}
pg.Init(r)
ren.HTML(w, http.StatusOK, "home", pg)
}