Introduction
The main content of this post is to introduce a bizdemo hertz_session
. The link to the demo is here.
This demo is designed to help users quickly get started with the Session middleware and CSRF middleware of the Hertz framework, and to show the distributed Session solution based on Redis.
If you don't know what Hertz is, then you can check out my previous articles which will help you get started with this Golang HTTP framework quickly.
The main features of the hertz_session
:
- Use
thrift
IDL to defineHTTP
interface - Use
hz
to generate code - Use
hertz-contrib/sessions
to store sessions - Use
hertz-contrib/csrf
to prevent Cross-Site Request Forgery attacks - Use
Gorm
andMySQL
- Use
AdminLTE
as frontend page
Hertz
Hertz is an ultra-large-scale enterprise-level microservice HTTP framework, featuring high ease of use, easy expansion, and low latency etc.
Hertz uses the self-developed high-performance network library Netpoll
by default. In some special scenarios, Hertz has certain advantages in QPS and latency compared to go net.
In internal practice, some typical services, such as services with a high proportion of frameworks, gateways and other services, after migrating Hertz, compared to the Gin framework, the resource usage is significantly reduced, CPU usage is reduced by 30%-60% with the size of the traffic.
For more details, see cloudwego/hertz.
How to get this demo
Use the following command to get hertz_session
:
git clone https://github.com/cloudwego/hertz-examples.git
cd bizdemo/hertz_session
Project structure
-
biz
holds the main business logic code, includinghandler
for HTTP requests,dal
for database operations, andmw
for middleware -
idl
storesthrift
IDL -
pkg
for some utility methods, business constants,errmsg
, template rendering, etc -
static
holds the frontend static files, all taken fromAdminLTE
- Other files in the root directory include the main startup file
main.go
, docker config files, etc
Use of middleware
Session middleware
The distributed session solution based on redis is to store the sessions of different servers in redis or redis cluster,
which aims to solve the problem that the sessions of multiple servers are not synchronized in the case of distributed system.
1. We store the session in Redis by using Hertz's session middleware, which is initialized as follows:
// biz/mw/session.go
func InitSession(h *server.Hertz) {
store, err := redis.NewStore(consts.MaxIdleNum, consts.TCP, consts.RedisAddr, consts.RedisPasswd, []byte(consts.SessionSecretKey))
if err != nil {
panic(err)
}
h.Use(sessions.New(consts.HertzSession, store))
}
- First connect to Redis using
redis.NewStore
by passing the address, password, etc - Use the Session middleware via the
h.Use
method and pass in the storage connection object that we just returned. The first parameter is the name of the Cookie, which we pass in as a defined constant
2. Once initialized, we can store the user's information (username in this case) in the session after the user is authenticated and logged in. Here's the core code for the Login
Handler Session:
// biz/handler/user/user_service.go/Login
session := sessions.Default(c)
session.Set(consts.Username, req.Username)
_ = session.Save()
- First get a Session object via
sessions.Default
- The username is stored in the Session using the
Set
method - Finally, we call
Save
to save the Session object
After the user is logged in, we will store a copy of the user's information in the session object stored in Redis, so the user can visit the corresponding page without logging in.
3. In this case, there is only one home page, index.html
, which will be checked if the user is logged in and redirected to the login page login.html
if they are not logged in. The core code to check if they are logged in is as follows:
// pkg/render/render.go
h.GET("/index.html", func(ctx context.Context, c *app.RequestContext) {
session := sessions.Default(c)
username := session.Get(consts.Username)
if username == nil {
c.HTML(http.StatusOK, "index.html", hutils.H{
"message": utils.BuildMsg(consts.PageErr),
})
c.Redirect(http.StatusMovedPermanently, []byte("/login.html"))
return
}
c.HTML(http.StatusOK, "index.html", hutils.H{
"message": utils.BuildMsg(username.(string)),
})
})
It's exactly the same as Login
in step 2, but instead of Set
, it's Get
and without Save
4. Finally, we need to clean up the user's session when the user logs out, the core code is as follows:
// biz/handler/user/user_service.go/Logout
session := sessions.Default(c)
session.Delete(consts.Username)
_ = session.Save()
Again, note that you need to Save
otherwise the delete operation will be invalid.
Session middleware encapsulates most of the complex logic that needs to be considered, such as the storage of different user sessions in Redis, and we only need to call a simple interface to complete the corresponding business process.
CSRF middleware
Next up is the use of CSRF middleware. The following explanation of what CSRF attacks are is taken from Wikipedia:
Cross-site request forgery, also known as one-click attack or session riding and abbreviated as CSRF (sometimes pronounced sea-surf[1]) or XSRF, is a type of malicious exploit of a website or web application where unauthorized commands are submitted from a user that the web application trusts.[2] There are many ways in which a malicious website can transmit such commands; specially-crafted image tags, hidden forms, and JavaScript fetch or XMLHttpRequests, for example, can all work without the user's interaction or even knowledge. Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user's browser.[3] In a CSRF attack, an innocent end user is tricked by an attacker into submitting a web request that they did not intend. This may cause actions to be performed on the website that can include inadvertent client or server data leakage, change of session state, or manipulation of an end user's account.
After reading the relevant information, I understand that malicious websites use the trust of some websites on the user's browser, such as the use of cookies, to launch some attacks.
Here we use CSRF middleware to protect registration, login POST form submission and POST logout. It is worth noting that the logout is initially using the GET method, but the browser sometimes caches the GET request, so there is a "bug" that fails to delete the Session. This problem was solved by changing the request method to POST and adding CSRF protection, check out this PR for details.
In this demo, since the CSRF middleware is added later, it was not considered when defining IDL at the beginning, so there is only one GET request to log out after login. Since GET is considered to be a safe method, here we mainly use the CSRF middleware to protect the registration and login two POST form submissions.
1. Let's also take a look the initialization and usage of CSRF as follows:
func InitCSRF(h *server.Hertz) {
h.Use(csrf.New(
csrf.WithSecret(consts.CSRFSecretKey),
csrf.WithKeyLookUp(consts.CSRFKeyLookUp),
csrf.WithNext(utils.IsLogout),
csrf.WithErrorFunc(func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusBadRequest, errors.New(consts.CSRFErr).Error())
c.Abort()
}),
))
}
- Call
h.Use
using CSRF middleware - Define the token with
csrf.WithSecret
- Retrieve the CSRF Token from the post form using the
csrf.WithKeyLookup
definition, default to the request header - Use
csrf.WithNext
to skip the no-login case, i.e. no Cookie exists - Custom exception handling with
csrf.WithErrorFunc
2. After initialization, we only need to submit the generated CSRF Token through the hidden
field when submitting the form after login, and the middleware will automatically help us verify whether it is valid. If there is an error, the exception handling function just defined will be used, because this Demo uses the template rendering mode. The core code is as follows (in the case of registration, the same goes for login) :
// pkg/render/render.go
h.GET("/register.html", func(ctx context.Context, c *app.RequestContext) {
if !utils.IsLogout(ctx, c) {
token = csrf.GetToken(c)
}
c.HTML(http.StatusOK, "register.html", hutils.H{
"message": utils.BuildMsg("Register a new membership"),
"token": utils.BuildMsg(token),
})
})
The core HTML template is as follows:
<div>
<input type="hidden" name="csrf" value="{[{ .token | BuildMsg }]}">
</div>
- After verifying that the user is logged in, we can get the corresponding token and put it in the form to submit, and let the middleware handle the rest.
Run the demo
Execute the following command to run the demo:
docker-compose up
go run .
then visit localhost:8888/register.html
to visit the register page, here are the sample pages:
Summary
That's all for this article, I would be happy if you found this helpful. Feel free to leave a comment if you have any questions.
Top comments (0)