前面我们已经初始化博客系统了,接着,我们再做管理员登录和权限控制判断。我们将分别介绍使用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看看效果,验证下登录过程是否正常。如果不出意外可以看到这样的画面:

 design-login

输入正确的账号、密码,就可以完成登录了。

完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/goblog 上查看,也可以直接fork一份来在上面做修改。