前面我们已经初始化博客系统了,接着,我们再做管理员登录和权限控制判断。我们将分别介绍使用sessions、cookie实现登录控制问题。
sessions的使用
前面我们在中间件环节的时候使用了sessions,并做了简单的介绍。我们这里在详细说明下,sessions如何使用。
使用session,需要先引入github.com/kataras/iris/v12/sessions
,这是一个sessions管理器。我们需要使用它来存储session数据。下面我们看看它是如何存储session的数据的。
我们需要在项目中初始化sessions:
var Sess = sessions.New(sessions.Config{Cookie: "irisweb"})
然后使用中间件来运行session,Sess.Start(ctx)
,Start函数需要接收一个context,然后我们定义一个字段hasLogin
,用来存储登录状态,如果hasLogin 为true,我们就通过ctx.Values().Set()
方法,将hasLogin
注入到context中,方便后面的控制器判断和检查调用。我们在中间件这一步不做拦截退出操作。
func Auth(ctx iris.Context) {
//检查登录状态
session := Sess.Start(ctx)
hasLogin := session.GetBooleanDefault("hasLogin", false)
ctx.Values().Set("hasLogin", hasLogin)
ctx.ViewData("hasLogin", hasLogin)
ctx.Next()
}
管理员登录
博客管理员的登录,得首先有一个可供登录的页面,以及处理登录的控制器。
管理员登录页面
我们在template文件夹下创建一个admin文件夹,并在里面新建一个login.html:
{% include "partial/header.html" %}
<div class="admin-login">
<div class="login-main">
<div class="login-box login-header">
<h2>{{SiteName}}h2>
<p>一个由irisweb框架编写的简单的博客系统p>
div>
<div class="login-box login-body layui-form">
<div class="layui-form-item">
<label class="login-icon layui-icon layui-icon-username" for="login-username">label>
<input type="text" name="user_name" id="login-username" lay-verify="required" placeholder="用户名" class="layui-input">
div>
<div class="layui-form-item">
<label class="login-icon layui-icon layui-icon-password" for="login-password">label>
<input type="password" name="password" id="login-password" lay-verify="required" placeholder="密码" class="layui-input">
div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login-submit">登 录button>
div>
div>
div>
{% include "partial/footer.html" %}
div>
上面是登录页面的html代码,同样地,我们引入了头部和尾部的代码片段。管理员登录表单包含两个字段,一个是管理员用户名(user_name),一个是管理员密码(password),它们都是必须字段。这里为了简单,没有加入验证码验证部分的代码。如果再严谨一点,这里是还需要添加验证码的。
提交表单是js代码
我们打开public/static/js/app.js
文件,添加监听点击登录按钮的js代码:
form.on('submit(login-submit)', function(obj){
$.post('/admin/login', obj.field, function(res) {
if(res.code === 0) {
layer.msg('登录成功', {
offset: '15px'
,icon: 1
,time: 1000
}, function(){
window.location.href = '/';
});
} else {
layer.msg(res.msg);
}
});
});
这里逻辑比较简单,监听到用户点击了登录按钮后(用户输入完按回车键也能监听到),通过post请求/admin/login
如果返回的res信息中,res.code 值为0,则表示登录成功,跳转到首页,否则就弹出后端返回的错误提示。
管理员登录页面的控制器
接着,我们在controller目录下,创建一个admin.go文件,用来存放登录页面的控制器和登录处理过程的控制器。在里面增加AdminLogin()函数:
func AdminLogin(ctx iris.Context) {
ctx.View("admin/login.html")
}
这个控制器很简单,只需要用来承载登录页面即可。
管理员登录处理逻辑控制器
接着,我们还需要创建一个接收登录页面提交上来的信息,完成登录过程。
我们先定义一个接收登录信息的结构体。我们在request文件夹下,新建一个Admin.go文件,添加如下代码:
package request
type Admin struct {
UserName string `form:"user_name" validate:"required"`
Password string `form:"password" validate:"required"`
}
登录表单提交的字段信息只有两个,分别是UserName和Password,它们都是必填的字段。
接着,我们开始编写登录处理逻辑控制器,在controller/admin.go文件里,添加AdminLoginForm()
函数:
func AdminLoginForm(ctx iris.Context) {
var req request.Admin
if err := ctx.ReadForm(&req); err != nil {
ctx.JSON(iris.Map{
"code": config.StatusFailed,
"msg": err.Error(),
})
return
}
admin, err := provider.GetAdminByUserName(req.UserName)
if err != nil {
ctx.JSON(iris.Map{
"code": config.StatusFailed,
"msg": err.Error(),
})
return
}
if !admin.CheckPassword(req.Password) {
ctx.JSON(iris.Map{
"code": config.StatusFailed,
"msg": "登录失败",
})
return
}
session := middleware.Sess.Start(ctx)
session.Set("hasLogin", true)
ctx.JSON(iris.Map{
"code": 0,
"msg": "登录成功",
"data": 1,
})
}
这个函数通过ctx.ReadForm
将前端提交的表单信息,读入到request.Admin结构体中,如果读取错误,则输出一个json错误信息。
接着通过用户名检查数据库中是否存在这个管理员,如果用户名错误,则返回错误。
如果前面判断都正常,则使用CheckPassword()
判断输入的密码是否正确。这个函数我们写在admin的模型中model/admin.go:
func (admin *Admin) CheckPassword(password string) bool {
if password == "" {
return false
}
byteHash := []byte(admin.Password)
bytePass := []byte(password)
err := bcrypt.CompareHashAndPassword(byteHash, bytePass)
if err != nil {
return false
}
return true
}
CheckPassword()
函数是Admin
模型的内置方法,它接收一个password
参数,通过golang.org/x/crypto/bcrypt
包的bcrypt.CompareHashAndPassword()
函数来验证admin中的bcrypt哈希密码admin.Password
与传入的password
明文密码是否等效,成功时返回零,失败时返回错误。我们判断错误是否为nil,有错误,则表示失败,返回false,其余返回true。
验证密码成功后,我们使用中间件已经声明的sessions存储器Sess
来设置登录状态:
session := middleware.Sess.Start(ctx)
session.Set("hasLogin", true)
退出登录控制器
上面已完成了登录的操作,我们还需要有一个退出操作。我们在controller/admin.go 中,添加AdminLogout()
函数来完成退出操作。
func AdminLogout(ctx iris.Context) {
session := middleware.Sess.Start(ctx)
session.Delete("hasLogin")
ctx.Redirect("/")
}
退出控制器不需要有前端页面,我们只需要将sessions中标记的hasLogin
移除即可,移除session需要使用到session.Delete()
函数,它接收的参数为需要移除的session字段。退出登录后,我们跳回到首页。
配置登录路由
上面登录页面和登录处理逻辑相关的控制器写好了,我们还需要将它注入到路由中,才能从浏览器中访问到。我们现在打开route/base.go文件,在Register中增加三个路由:
admin := app.Party("/admin", controller.Inspect)
{
admin.Get("/login", controller.AdminLogin)
admin.Post("/login", controller.AdminLoginForm)
admin.Get("/logout", controller.AdminLogout)
}
这里,因为/admin是一个路径前缀,以admin开头的路径,都是处理登录相关的路径,因此,我们使用app.Party()
将它们归类为一个路由组。这里我们甚至还可以使用更复杂的功能,比如将它绑定到一个二级域名下。我们为了简洁,暂时不采用二级域名操作。
验证结果
我们重启一下项目,在浏览器中访问http://127.0.0.1:8001/admin/login
看看效果,验证下登录过程是否正常。如果不出意外可以看到这样的画面:
输入正确的账号、密码,就可以完成登录了。
完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/goblog 上查看,也可以直接fork一份来在上面做修改。